C# IQueryable上的动态LINQ聚合作为单个查询
我正在构建一个datagrid库,它在通用IQueryable数据源上工作。在底部,所选列将具有聚合:总和、平均值、计数等 我可以使用本文中的代码单独计算总和/平均数/计数 我不想为一个数据源单独运行它们,因为这会导致对数据库进行多个查询,我宁愿创建一个表达式树,并将其作为一个查询执行 在静态LINQ中,您将执行所有的.Sum、.Average和.Count方法,并返回一个新的匿名类型和值。我不需要匿名类型(除非这是唯一的方法):聚合的列表或数组就可以了C# IQueryable上的动态LINQ聚合作为单个查询,c#,linq,dynamic-linq,C#,Linq,Dynamic Linq,我正在构建一个datagrid库,它在通用IQueryable数据源上工作。在底部,所选列将具有聚合:总和、平均值、计数等 我可以使用本文中的代码单独计算总和/平均数/计数 我不想为一个数据源单独运行它们,因为这会导致对数据库进行多个查询,我宁愿创建一个表达式树,并将其作为一个查询执行 在静态LINQ中,您将执行所有的.Sum、.Average和.Count方法,并返回一个新的匿名类型和值。我不需要匿名类型(除非这是唯一的方法):聚合的列表或数组就可以了 我假设在另一篇文章中,我需要以某种方式将
我假设在另一篇文章中,我需要以某种方式将一系列MethodCallExpression对象串在一起。有人能帮忙吗?我找到了另一种方法,它使用动态LINQ库,避免了构建复杂的表达式树 对于任何感兴趣的人,解决方案都在下面的单元测试中。我有一个名为TestQueryableDataset的随机数据集。此IQueryable数据源的泛型类型具有Total属性(decimal)、折扣属性(nullable decimal)和ID属性(int) 单元测试首先使用静态LINQ查询获得预期结果 然后它构造一个select语句,该语句使用groupby变量“It”来计算总和、平均值和计数。属性名通过字符串传递,以证明它是字符串类型的 GroupBy方法.GroupBy(x=>1)是一个虚拟分组,用于使聚合应用于整个数据集 请注意,这将返回一个具有属性t0、t1和t2的动态结果。但是,groupby/select操作仍然返回IQueryable,但只返回一个结果。我们必须先使用t.Cast();转换为对象数组,然后获得第一个结果 然后,我们可以使用反射来获得每个结果(t0、t1、t2)的属性作为实际值,并断言它们与我们之前得到的静态结果相匹配
[TestMethod()]
[TestProperty("Anvil.DataSets", "QueryableExtensions")]
public void DynamicAggregate_test()
{
var source = new Anvil.Test.DataSets.TestQueryableDataset();
var data = source.GetData();
var expectedTotal = (from d in data select d.Total).Sum();
var expectedDiscount = (from d in data select d.Discount).Average();
var expectedCount = (from d in data select d.ID).Count();
const string prop0 = "Total";
const string prop1 = "Discount";
const string prop2 = "ID";
string sumExpr = string.Format("new ( Sum(it.{0}) as t0, Average(it.{1}) as t1 , Count() as t2)", prop0,prop1, prop2);
var t = data.GroupBy(x => 1).Select(sumExpr);
var firstItem = t.Cast<object>().First();
var ttype = firstItem.GetType();
var p0 = ttype.GetProperty("t0");
var p1 = ttype.GetProperty("t1");
var p2 = ttype.GetProperty("t2");
decimal actualTotal = (decimal)(p0.GetValue(firstItem));
decimal actualDiscount = (decimal)(p1.GetValue(firstItem));
int actualCount = (int)(p2.GetValue(firstItem));
Assert.AreEqual(expectedTotal, actualTotal);
Assert.AreEqual(expectedDiscount, actualDiscount);
Assert.AreEqual(expectedCount, actualCount);
}
[TestMethod()]
[TestProperty(“Anvil.dataset”、“QueryableExtensions”)]
公共无效动态GREGATE_测试()
{
var source=new Anvil.Test.DataSets.TestQueryableDataset();
var data=source.GetData();
var expectedTotal=(从数据中的d选择d.Total).Sum();
var expectedDiscount=(从数据中的d选择d.Discount).Average();
var expectedCount=(从数据中的d选择d.ID).Count();
常量字符串prop0=“总计”;
const string prop1=“折扣”;
常量字符串prop2=“ID”;
string sumExpr=string.Format(“new(Sum(it.{0})作为t0,Average(it.{1})作为t1,Count()作为t2)”,prop0,prop1,prop2;
var t=data.GroupBy(x=>1);
var firstItem=t.Cast().First();
var ttype=firstItem.GetType();
var p0=ttype.GetProperty(“t0”);
var p1=ttype.GetProperty(“t1”);
var p2=ttype.GetProperty(“t2”);
decimal actualTotal=(十进制)(p0.GetValue(第一项));
decimal ActualDiscovery=(十进制)(p1.GetValue(第一项));
int actualCount=(int)(p2.GetValue(firstItem));
等于(预期总数、实际总数);
Assert.AreEqual(预期结算、实际结算);
Assert.AreEqual(预期计数、实际计数);
}
另见:
- 您不需要匿名类型。您只需要一个具有3个属性的类型
Sum
、Count
和Average
<代码>总和和平均值
类型在设计时未知。因此,对这两个属性使用Object
typeCount
始终是一个int
public class Aggregation
{
public Aggregation(object sum, object average, int count)
{
Sum = sum;
Average = average;
Count = count;
}
public object Sum { get; private set; }
public object Average { get; private set; }
public int Count { get; private set; }
}
与本文中描述的Sum
扩展方法类似,您可以编写一个Aggregate
扩展方法,该方法从IQueryable
集合和属性名称计算Aggregation
类实例。
真正的困难在于确定与属性类型匹配的平均重载方法。重载不能从返回类型确定,而是从用作第二个参数的lambda表达式的返回类型确定
例如,如果属性类型是int,则代码必须选择公共静态双平均值(
这是可靠的消息来源,
表达式选择器
)
过载
public static Aggregation Aggregate(this IQueryable source, string member)
{
if (source == null)
throw new ArgumentNullException("source");
if (member == null)
throw new ArgumentNullException("member");
// Properties
PropertyInfo property = source.ElementType.GetProperty(member);
ParameterExpression parameter = Expression.Parameter(source.ElementType, "s");
Expression selector = Expression.Lambda(Expression.MakeMemberAccess(parameter, property), parameter);
// We've tried to find an expression of the type Expression<Func<TSource, TAcc>>,
// which is expressed as ( (TSource s) => s.Price );
// Methods
MethodInfo sumMethod = typeof(Queryable).GetMethods().First(
m => m.Name == "Sum"
&& m.ReturnType == property.PropertyType // should match the type of the property
&& m.IsGenericMethod);
MethodInfo averageMethod = typeof(Queryable).GetMethods().First(
m => m.Name == "Average"
&& m.IsGenericMethod
&& m.GetParameters()[1]
.ParameterType
.GetGenericArguments()[0]
.GetGenericArguments()[1] == property.PropertyType);
MethodInfo countMethod = typeof(Queryable).GetMethods().First(
m => m.Name == "Count"
&& m.IsGenericMethod);
return new Aggregation(
source.Provider.Execute(
Expression.Call(
null,
sumMethod.MakeGenericMethod(new[] { source.ElementType }),
new[] { source.Expression, Expression.Quote(selector) })),
source.Provider.Execute(
Expression.Call(
null,
averageMethod.MakeGenericMethod(new[] { source.ElementType }),
new[] { source.Expression, Expression.Quote(selector) })),
(int)source.Provider.Execute(
Expression.Call(
null,
countMethod.MakeGenericMethod(new[] { source.ElementType }),
new[] { source.Expression })));
}
公共静态聚合聚合(此IQueryable源,字符串成员)
{
if(source==null)
抛出新的ArgumentNullException(“源”);
if(成员==null)
抛出新的异常(“成员”);
//性质
PropertyInfo property=source.ElementType.GetProperty(成员);
ParameterExpression参数=Expression.parameter(source.ElementType,“s”);
表达式选择器=Expression.Lambda(Expression.MakeMemberAccess(参数,属性),参数);
//我们试图找到表达式类型的表达式,
//表示为((t来源s)=>s.价格);
//方法
MethodInfo sumMethod=typeof(Queryable).GetMethods().First(
m=>m.Name==“总和”
&&m.ReturnType==property.PropertyType//应与属性的类型匹配
&&m.一般方法);
MethodInfo averageMethod=typeof(Queryable).GetMethods().First(
m=>m.Name==“平均”
&&m.IsGenericMethod
&&m.GetParameters()[1]
.参数类型
.GetGenericArguments()[0]
.GetGenericArguments()[1]==property.PropertyType);
MethodInfo countMethod=typeof(Queryable).GetMethods().First(
m=>m.Name==“计数”
&&m.一般方法);
返回新的聚合(
source.Provider.Execute(
表情,打电话(
无效的
SummMethod.MakeGenericMethod(新[]{source.Element
public static object AggregateFunc(this IQueryable source, string function, string member)
{
if (source == null) throw new ArgumentNullException("source");
if (member == null) throw new ArgumentNullException("member");
// Properties
PropertyInfo property = source.ElementType.GetProperty(member);
ParameterExpression parameter = Expression.Parameter(source.ElementType, "s");
// We've tried to find an expression of the type Expression<Func<TSource, TAcc>>,
// which is expressed as ( (TSource s) => s.Price );
Type propertyType = property.PropertyType;
Type convertPropType = property.PropertyType;
if (function == "Sum")//convert int to bigint
{
if (propertyType == typeof(Int32))
convertPropType = typeof(Int64);
else if (propertyType == typeof(Int32?))
convertPropType = typeof(Int64?);
}
Expression selector = Expression.Lambda(Expression.Convert(Expression.MakeMemberAccess(parameter, property), convertPropType), parameter);
//var methods = typeof(Queryable).GetMethods().Where(x => x.Name == function);
// Method
MethodInfo aggregateMethod = typeof(Queryable).GetMethods().SingleOrDefault(
m => m.Name == function
&& m.IsGenericMethod
&& m.GetParameters().Length == 2 && m.GetParameters()[1].ParameterType.GenericTypeArguments[0].GenericTypeArguments[1] == convertPropType);// very hacky but works :)
MethodCallExpression callExpr;
// Sum, Average
if (aggregateMethod != null)
{
callExpr = Expression.Call(
null,
aggregateMethod.MakeGenericMethod(new[] { source.ElementType }),
new[] { source.Expression, Expression.Quote(selector) });
return source.Provider.Execute(callExpr);
}
// Min, Max
else
{
aggregateMethod = typeof(Queryable).GetMethods().SingleOrDefault(
m => m.Name == function
&& m.GetGenericArguments().Length == 2
&& m.IsGenericMethod);
if (aggregateMethod != null)
{
callExpr = Expression.Call(
null,
aggregateMethod.MakeGenericMethod(new[] { source.ElementType, propertyType }),
new[] { source.Expression, Expression.Quote(selector) });
return source.Provider.Execute(callExpr);
}
}
return null;
}