【问题标题】:How to dynamically add properties to a LINQ GroupBy clause如何动态地将属性添加到 LINQ GroupBy 子句
【发布时间】:2012-11-20 20:45:01
【问题描述】:

我有一堂课Product:

class Product
{ 
   int Id { get; set; }
   string Name { get; set; }
   int CategoryId { get; set; }
   int PlantId { get; set; }
   DateTime ProductionDate { get; set; }
}

我想在多个属性上使用LINQ GroupBy,但我事先不知道有多少以及哪些属性。例如,我可能只想按CategoryIdPlantId 或两者进行分组。我在网上找到了一篇描述如何use LINQ GrouBy dinamically的文章。

这确实可能很好用,但如果我想在不知道粒度的情况下在 ProductionDate.YearProductionDate.Month 上执行 Group By?作为粒度,我的意思是我是要对特定年份产生的所有Products 进行分组,还是将分组缩小到月份。

我找到的唯一合乎逻辑的解决方案是:

public ProductInfo GetGroupedProducts(int? year, int? month, int? selectedCategoryId, int? selectedPlantId)
{
List<Product> products = GetProducts();

var groupedProducts = products.GroupBy(p => new { (year == null ? p.ProductionDate.Year : string.Empty),
                                                  (month == null ? p.ProductionDate.Month : string.Empty),
                                                  (selectedCategoryId == null ? p.CategoryId : string.Empty),
                                                  (selectedPlantId == null ? p.PlantId : string.Empty)
});

//perform some additional filtering and assignments 

}

但我想可能会有更清洁、更合适的解决方案。使用基于字符串的旧式查询构建方式,这项任务更容易完成。如果没有其他办法,我真的认为这是LINQ的一部分需要改进。

【问题讨论】:

  • 好吧,我会使用 bool 参数而不是 int?...
  • 一个返回void的方法GetGroupedProducts?
  • @Rawling:我使用int?因为在 GroupBy 之后我还执行了一些过滤
  • @TimSchmelter:我可以更改返回类型,但我认为它与问题的上下文无关
  • 由于您已经为每个可能要分组的字段传递了一个参数,因此您可以做任何非常动态的事情 - 或者更确切地说,需要 i> 做。 (与此相反,例如,如果您要传入字段名称的 string[],则需要使用反射或类似方法来获取正确的属性。)

标签: linq dynamic group-by


【解决方案1】:

更干净的解决方案是使用这种扩展方法:

public static TResult With<TInput, TResult>(this TInput? o, Func<TInput, TResult>
    selector, TResult defaultResult = default(TResult)) where TInput : struct
{
    return o.HasValue ? selector(o.Value) : defaultResult;
}

像这样:

string result = year.With(T => p.ProductionDate.Year, string.Empty);

其中,如果空值是可以的:

string result = year.With(T => p.ProductionDate.Year);

或带有T 的东西,即int,以防int? 有价值。


但是,你知道,更好的解决方案是out there,所以请随意扩展你的代码,以便我分析它。

【讨论】:

  • 您的泛型类型不一致 - oint?selectorFunc&lt;Product, int&gt;defaultResultstring。 (尽管不可否认,TResult 的分歧来自问题。)
  • @Rawling 因为 p 用于 Linq 查询,而 T 是不同的 lambda。两者互不干扰。
  • 啊,我明白你的意思了。尽管如此,它还是说明了很多原始代码对我来说是显而易见的,而这让我感到困惑。这实际上只是三元运算的包装器。
  • (实际上,如果您只是将其称为SelectOrDefault 或类似的名称,我可能会理解得更快:))
  • @Rawling 不仅仅是一个包装器。它对对象遍历提供了完整的流式语法支持。叫the maybe monad
【解决方案2】:

如果我理解您的问题,我遇到了类似的问题Reversing typeof to use Linq Field<T>

我会做类似的事情

public static IEnumerable<IGrouping<string, TElement>> GroupMany<TElement>(
    this IEnumerable<TElement> elements, 
    params Func<TElement, object>[] groupSelectors)
    {
        return elements.GroupBy(e => string.Join(":", groupSelectors.Select(s => s(e))));
    }

然后你可以像这样调用你的函数

var groupedProducts = products.GroupMany(p => p.CategoryId , p => p.ProductionDate.Month);

函数通过用冒号分隔的属性字符串进行分组。我这样做的原因是保证字符串的哈希码与类的哈希码相同。

【讨论】:

    猜你喜欢
    • 2018-07-17
    • 2010-09-15
    • 1970-01-01
    • 2011-01-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-01-12
    • 1970-01-01
    相关资源
    最近更新 更多