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

百般思念 提交于 2019-12-24 07:04:18

问题


I programmed the following query which selects the grouped data key columns and sums the Amount column. It works perfect.

    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;
    }

The query uses the following types to group and sum.

    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; }
    }

As the next step I want to program the equivalent query using the Linq Expressions.
I faced an issue that I do not know how to create MethodCallExpression for:
g.Sum(row => row.Field("Amount"))

I programmed the code below. I marked in the comments where I'm stuck.

    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;
    }

Could anybody help me please how to construct the call expression for Sum() ?


回答1:


I've made some changes to your code:

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;
}

There is a wonderful overload of Expression.Call that finds and handles generic methods, and you don't need an array/List<> to call Expression.Call, because it has a params overload.

Note that I've changed your code to Enumerable... I don't think you can do what you want with Queryable... But you can try to change it back. Note even that, while you have tried to make the code "universal" on the type of AggN0 (the PropertyInfo propertyInfo that is used only to discover the type of AggN0), the double keyword appears in places where it is difficult to remove (the return type of the GetFunc() method)



来源:https://stackoverflow.com/questions/50623451/c-sharp-linq-methodcallexpression-for-sum-datarow-used-for-groupedby-select

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!