具有动态列组的 LINQ GroupBy
Posted
技术标签:
【中文标题】具有动态列组的 LINQ GroupBy【英文标题】:LINQ GroupBy with a dynamic group of columns 【发布时间】:2015-10-22 16:45:59 【问题描述】:我有一张这样的桌子:
variety category quantity
----------------------------------------------
rg pm 10
gs pm 5
rg com 8
我想根据这些bool
参数制作一个GroupBy
:
IncludeVariety
IncludeCategory
例如:
IncludeVariety = true;
IncludeCategory = true;
会返回这个:
variety category quantity
----------------------------------------------
rg pm 10
gs pm 5
rg com 8
还有这个:
IncludeVariety = true;
IncludeCategory = false;
会返回这个:
variety category quantity
----------------------------------------------
rg - 18
gs - 5
还有这个:
IncludeVariety = false;
IncludeCategory = true;
会返回这个:
variety category quantity
----------------------------------------------
- pm 15
- com 8
你懂的……
问题:如何使用 LINQ 实现这一目标?
重要提示:我已将问题简化为 两个布尔变量(IncludeVariety
和 IncludeCategory
),但实际上我会有更多列 (说五个)
我不知道如何动态生成查询(.GroupBy
和 .Select
):
rows.GroupBy(r => new r.Variety, r.Category )
.Select(g => new
Variety = g.Key.Variety,
Category = g.Key.Category,
Quantity = g.Sum(a => a.Quantity),
);
rows.GroupBy(r => new r.Category )
.Select(g => new
Variety = new ,
Category = g.Key.Category,
Quantity = g.Sum(a => a.Quantity),
);
rows.GroupBy(r => new r.Variety )
.Select(g => new
Variety = g.Key.Variety,
Category = new ,
Quantity = g.Sum(a => a.Quantity),
);
我过去做过的类似的事情是连接Where
,比如:
var query = ...
if (foo)
query = query.Where(...)
if (bar)
query = query.Where(...)
var result = query.Select(...)
我可以在这里做类似的事情吗?
【问题讨论】:
【参考方案1】:var results=items
.Select(i=>
new
variety=includevariety?t.variety:null,
category=includecategory?t.category:null,
...
)
.GroupBy(g=>
new variety, category, ... , g=>g.quantity)
.Select(i=>new
variety=i.Key.variety,
category=i.Key.category,
...
quantity=i.Sum()
);
缩短:
var results=items
.GroupBy(g=>
new
variety=includevariety?t.variety:null,
category=includecategory?t.category:null,
...
, g=>g.quantity)
.Select(i=>new
variety=i.Key.variety,
category=i.Key.category,
...
quantity=i.Sum()
);
【讨论】:
你是个天才!我会试试这个并接受答案以防万一 如果您愿意,也可以通过组合第一个 select 和 groupby 来缩短它,但我认为这更具可读性,但这是非常主观的。 哦,好的。我忘了空条件。我删除了我的答案xD 伙计,它就像一个魅力!我也有一些 id,所以如果有人感兴趣:Category = includeCategory ? r.Category : null, CategoryId = includeCategory ? r.CategoryId : (long?)null
-- 需要(long?)null
【参考方案2】:
如果您需要它是真正动态的,请使用 Scott Gu 的 Dynamic LINQ 库。
您只需要弄清楚要在结果中包含哪些列并按它们分组。
public static IQueryable GroupByColumns(this IQueryable source,
bool includeVariety = false,
bool includeCategory = false)
var columns = new List<string>();
if (includeVariety) columns.Add("Variety");
if (includeCategory) columns.Add("Category");
return source.GroupBy($"new(String.Join(",", columns))", "it");
然后你可以将它们分组。
var query = rows.GroupByColumns(includeVariety: true, includeCategory: true);
【讨论】:
如果您需要动态结果,这是一个很好的解决方案。我通常会尽可能避免这种情况,但有时你就是做不到,这就是这个答案的完美之处。 为什么比你的答案更好?【参考方案3】:在任何情况下,仍然需要在没有动态 LINQ 和类型安全的情况下按动态列进行分组。您可以向此 IQueryable 扩展方法提供一个匿名对象,其中包含您可能想要分组的所有属性(具有匹配的类型!)以及您想要用于此组调用的属性名称列表。在第一个重载中,我使用匿名对象来获取它的构造函数和属性。我使用它们通过表达式动态地构建组。在第二个重载中,我将匿名对象仅用于 TKey 的类型推断,不幸的是,在 C# 中没有办法解决,因为它具有有限的类型别名能力。仅适用于像这样的可为空的属性,可能很容易为非空值扩展,但现在不能打扰
public static IQueryable<IGrouping<TKey, TElement>> GroupByProps<TElement, TKey>(this IQueryable<TElement> self, TKey model, params string[] propNames)
var modelType = model.GetType();
var props = modelType.GetProperties();
var modelCtor = modelType.GetConstructor(props.Select(t => t.PropertyType).ToArray());
return self.GroupByProps(model, modelCtor, props, propNames);
public static IQueryable<IGrouping<TKey, TElement>> GroupByProps<TElement, TKey>(this IQueryable<TElement> self, TKey model, ConstructorInfo modelCtor, PropertyInfo[] props, params string[] propNames)
var parameter = Expression.Parameter(typeof(TElement), "r");
var propExpressions = props
.Select(p =>
Expression value;
if (propNames.Contains(p.Name))
value = Expression.PropertyOrField(parameter, p.Name);
else
value = Expression.Convert(Expression.Constant(null, typeof(object)), p.PropertyType);
return value;
)
.ToArray();
var n = Expression.New(
modelCtor,
propExpressions,
props
);
var expr = Expression.Lambda<Func<TElement, TKey>>(n, parameter);
return self.GroupBy(expr);
我实现了两个重载,以防您想缓存构造函数和属性以避免每次调用时的反射。像这样使用:
//Class with properties that you want to group by
class Record
public string Test get; set;
public int? Hallo get; set;
public DateTime? Prop get; set;
public string PropertyWhichYouNeverWantToGroupBy get; set;
//usage
IQueryable<Record> queryable = ...; //the queryable
var grouped = queryable.GroupByProps(new
Test = (string)null, //put all properties that you might want to group by here
Hallo = (int?)null,
Prop = (DateTime?)null
, nameof(Record.Test), nameof(Record.Prop)); //This will group by Test and Prop but not by Hallo
//Or to cache constructor and props
var anonymous = new
Test = (string)null, //put all properties that you might want to group by here
Hallo = (int?)null,
Prop = (DateTime?)null
;
var type = anonymous.GetType();
var constructor = type.GetConstructor(new[]
typeof(string), //Put all property types of your anonymous object here
typeof(int?),
typeof(DateTime?)
);
var props = type.GetProperties();
//You need to keep constructor and props and maybe anonymous
//Then call without reflection overhead
queryable.GroupByProps(anonymous, constructor, props, nameof(Record.Test), nameof(Record.Prop));
您将作为 TKey 接收的匿名对象将仅填充您用于分组的键(即在此示例中为“测试”和“道具”),其他的将为空。
【讨论】:
以上是关于具有动态列组的 LINQ GroupBy的主要内容,如果未能解决你的问题,请参考以下文章