我正在构建一个适用于通用IQueryable数据源的数据网格库。在底部选定的列将具有聚合:总和,平均值,计数等。
我可以使用本文How to do a Sum using Dynamic LINQ
中的代码单独计算总和/平均值/计数我不想为数据源单独运行它们,因为这会导致对数据库进行多次查询,我宁愿创建一个表达式树,并将其作为单个查询执行。
在静态LINQ中,您将执行所有.Sum,.Average和.Count方法,并返回带有值的新匿名类型。我不需要匿名类型(除非这是唯一的方法):聚合的列表或数组都没问题。
我假设从另一篇文章中我需要以某种方式将一系列MethodCallExpression对象串起来。有人可以帮忙吗?
答案 0 :(得分:2)
我找到了一种使用Dynamic LINQ库的替代方法,避免了构造复杂的表达式树。
对于任何感兴趣的人,解决方案都在下面的单元测试中。我有一个名为TestQueryableDataset的随机数据集。此IQueryable数据源的泛型类型具有Total属性(十进制),Discount属性(可为空的十进制)和ID属性(int)。
单元测试首先使用静态LINQ查询获得预期结果。
然后构造一个select语句,该语句使用groupby变量'it'来计算总和,平均值和计数。属性名称由字符串传递,以证明这是字符串类型。
分组方法 .GroupBy(x => 1)是一个虚拟分组,可以将聚合应用于整个数据集。
请注意,这将返回具有属性t0,t1和t2的单个动态结果。但是,groupby / select操作仍然返回IQueryable但只有一个结果。我们必须使用t.Cast()。First();转换为对象数组,然后得到第一个结果。
然后我们可以使用反射来获取每个结果的属性(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);
}
另见:
答案 1 :(得分:2)
您不需要匿名类型。您只需要一个包含3个属性Sum
,Count
和Average
的类型。设计时不知道Sum
和Average
类型。因此,对这两个属性使用Object
类型。 Count
始终为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; }
}
与文章How to do a Sum using Dynamic LINQ中描述的Sum
扩展方法类似,您可以编写Aggregate
扩展方法,从Aggregation
计算IQueryable
类实例集合和属性名称。
真正的困难在于确定与属性类型匹配的平均重载方法。无法从返回类型确定重载,而是从用作第二个参数的lambda表达式的返回类型确定。
例如,如果属性类型是int,则代码必须选择public static double Average<TSource>(
this IQueryable<TSource> source,
Expression<Func<TSource, int>> selector
)
重载。
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 })));
}
答案 2 :(得分:0)
这是我的sum,average和min,max的解决方案..这是我在其中一个项目中使用的。
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;
}