c#linq用于.GroupedBy()。Select()的Sum(Da??taRow)的MethodCallExpression

c# linq MethodCallExpression for Sum( DataRow) used for .GroupedBy().Select()

我对以下查询进行了编程,该查询选择了分组的数据关键字列并对Amount列求和。效果很好。

1
2
3
4
5
6
7
8
9
10
11
    private static IEnumerable<GroupSum> GetListOfGroupedRows(IEnumerable<IGrouping<GroupKey, DataRow>> queryGroup)
    {
        IEnumerable<GroupSum> querySelect = queryGroup
            .Select(g => new GroupSum
            {
                KeyS0 = g.Key.KeyS0,
                KeyS1 = g.Key.KeyS1,
                AggN0 = g.Sum(row => row.Field<double>("Amount"))
            });
        return querySelect;
    }

查询使用以下类型进行分组和求和。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
    private class GroupKey : IEquatable<GroupKey>
    {
        public string KeyS0 { get; set; }
        public string KeyS1 { get; set; }

        public bool Equals(GroupKey other)
        {
            if (ReferenceEquals(null, other))
                return false;
            if (ReferenceEquals(this, other))
                return true;
            return string.Equals(this.KeyS0, other.KeyS0) &&
                    string.Equals(this.KeyS1, other.KeyS1);
        }

        public override int GetHashCode()
        {
            int hash0 = this.KeyS0 == null ? 0 : this.KeyS0.GetHashCode();
            int hash1 = this.KeyS1 == null ? 0 : this.KeyS1.GetHashCode();
            return hash0 + 31 * hash1;
        }
    }

    private class GroupSum : GroupKey
    {
        public Double AggN0 { get; set; }
    }

下一步,我想使用Linq表达式对等效查询进行编程。
我遇到了一个我不知道如何为以下对象创建MethodCallExpression的问题:
g.Sum(row => row.Field(" Amount "))

我编写了以下代码。我在卡住的评论中标记了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
    private static void GetListOfGroupedRowsExpress()
    {
        //The MethodInfo for generic Field< T >(DataRow, String) can be retrieved by:
        MethodInfo methInfo = typeof(DataRowExtensions).GetMethod("Field", new Type[] { typeof(DataRow), typeof(string) });

        ParameterExpression expRow = Expression.Parameter(typeof(DataRow),"row");  //Parametr: (row =>....)

        //Property to bind
        PropertyInfo propertyInfo = typeof(GroupSum).GetProperty("AggN0");

        //This returns properly: row.Field<double>("Amount")
        MethodCallExpression expCall = GetFieldCallExpression(expRow, methInfo, propertyInfo.PropertyType,"Amount");

        //This returns properly:  row => row.Field<double>("Amount")
        LambdaExpression expRowValues = Expression.Lambda<Func<DataRow, double>>(expCall, expRow);

        NewExpression expNewGroupKey = Expression.New(typeof(GroupSum));
        ParameterExpression expG = Expression.Parameter(typeof(GroupSum),"g");

        //This returns properly method info for: double Sum< T >()
        MethodInfo methodInfoSum = typeof(Queryable).GetMethods().First(m =>
            m.Name =="Sum"
            && m.ReturnType == typeof(double)
            && m.IsGenericMethod
            );
        //This returns properly method info for: double Sum<DataRow>()
        MethodInfo methodInfoSumDataRow = methodInfoSum.MakeGenericMethod(new Type[] { typeof(DataRow) });

        //And here I'm stuck. The code below compiles but at runtime it throws an error:
        //Expression of type 'TestLinq.TestLinqDataTable+GroupSum' cannot be used for parameter of type 'System.Linq.IQueryable`1[System.Data.DataRow]' of method 'Double Sum[DataRow](System.Linq.IQueryable`1[System.Data.DataRow], System.Linq.Expressions.Expression`1[System.Func`2[System.Data.DataRow,System.Double]])'
        MethodCallExpression expSumRows = Expression.Call(
            null,
            methodInfoSumDataRow,
            expG,
            expRowValues);
    }

    private static MethodCallExpression GetFieldCallExpression(ParameterExpression expRow, MethodInfo methodFieldGeneric,
                                                                Type type, string columnName)
    {
        List<Expression> list = new List<Expression>();
        list.Add(expRow);

        ConstantExpression expColumnName = Expression.Constant(columnName, typeof(string));
        list.Add(expColumnName);

        MethodInfo methodFieldTyped = methodFieldGeneric.MakeGenericMethod(type);

        MethodCallExpression expCall = Expression.Call(null, methodFieldTyped, list);
        return expCall;
    }

有人可以帮助我如何构造Sum()的调用表达式吗?


我对您的代码进行了一些更改:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
private static Func<IGrouping<GroupKey, DataRow>, double> GetFunc()
{
    //row => row.Field<double>("Amount")
    //The MethodInfo for generic Field< T >(DataRow, String) can be retrieved by:
    MethodInfo methInfo = typeof(DataRowExtensions).GetMethod("Field", new Type[] { typeof(DataRow), typeof(string) });

    ParameterExpression expRow = Expression.Parameter(typeof(DataRow),"row");  //Parametr: (row =>....)

    //Property to bind
    PropertyInfo propertyInfo = typeof(GroupSum).GetProperty(nameof(GroupSum.AggN0));

    //This returns properly: row.Field<double>("Amount")
    MethodCallExpression expCall = GetFieldCallExpression(expRow, methInfo, propertyInfo.PropertyType,"Amount");

    //This returns properly:  row => row.Field<double>("Amount")
    var expRowValues = Expression.Lambda(expCall, expRow);

    ParameterExpression expQuerygroup = Expression.Parameter(typeof(IGrouping<GroupKey, DataRow>),"g");

    MethodCallExpression expSumRows = Expression.Call(typeof(Enumerable), nameof(Enumerable.Sum), new[] { expRow.Type }, expQuerygroup, expRowValues);

    var sum = Expression.Lambda<Func<IGrouping<GroupKey, DataRow>, double>>(expSumRows, expQuerygroup);
    return sum.Compile();
}

private static MethodCallExpression GetFieldCallExpression(ParameterExpression expRow, MethodInfo methodFieldGeneric, Type type, string columnName)
{
    ConstantExpression expColumnName = Expression.Constant(columnName, typeof(string));

    MethodInfo methodFieldTyped = methodFieldGeneric.MakeGenericMethod(type);

    MethodCallExpression expCall = Expression.Call(null, methodFieldTyped, expRow, expColumnName);
    return expCall;
}

Expression.Call有一个奇妙的重载,它查找并处理通用方法,并且您不需要array / List<>来调用Expression.Call,因为它具有params重载。

请注意,我已将您的代码更改为Enumerable ...我认为您无法使用Queryable做您想做的事情...但是您可以尝试将其改回。甚至还要注意,当您尝试使AggN0类型(仅用于发现AggN0类型的PropertyInfo propertyInfo)代码为"通用"时,double关键字会出现在以下位置:很难删除(GetFunc()方法的返回类型)