Dynamic LINQ aggregates on IQueryable as a single query

后端 未结 3 743
一个人的身影
一个人的身影 2021-01-06 20:06

I\'m building a datagrid library that works on generic IQueryable data sources. At the bottom selected columns will have aggregates: sum, average, count etc.

I can c

3条回答
  •  春和景丽
    2021-01-06 20:42

    You don't need anonymous type. You just need a type with the 3 properties Sum, Count and Average. Sum and Average type aren't known at design time. So, use Object type for these 2 properties. Count is always an 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; }
    }
    

    Like the Sum extension method described in the article How to do a Sum using Dynamic LINQ, you can write an Aggregate extension method which compute an Aggregation class instance from a IQueryable collection and a property name. The real difficulty is about determining the Average overload method which match the property type. Overload can't be determined from the return type but from the return type of the lambda expression used as second argument.

    For example, if the property type is an int, code has to select the public static double Average( this IQueryable source, Expression> selector ) overload.

    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>,
        // 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 })));
    }
    

提交回复
热议问题