我有一张这样的表:
variety category quantity
----------------------------------------------
rg pm 10
gs pm 5
rg com 8
我想根据这些GroupBy
参数制作bool
:
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实现此目的?
重要提示:我已将问题减少为两个bool变量(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(...)
我可以在这做些什么吗?
答案 0 :(得分:8)
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()
});
答案 1 :(得分:6)
如果您需要真正动态,请使用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);
答案 2 :(得分:0)
在任何情况下,仍然有人需要按没有动态LINQ和类型安全性的动态列进行分组。您可以为该IQueryable扩展方法提供一个匿名对象,该对象包含您可能想要分组的所有属性(具有匹配类型!)以及要用于此组调用的属性名称列表。在第一个重载中,我使用匿名对象获取其构造函数和属性。我使用这些来动态地通过表达式构建组。在第二次重载中,我仅将匿名对象用于TKey的类型推断,不幸的是,由于C#具有有限的类型别名功能,因此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收到的匿名对象将仅填充用于分组的键(在此示例中为“ Test”和“ Prop”),其他键为null。