c # linq MethodCallExpression для суммы (DataRow), используемой для .GroupedBy (). Select () - PullRequest
0 голосов
/ 31 мая 2018

Я запрограммировал следующий запрос, который выбирает столбцы сгруппированных ключей данных и суммирует столбец Сумма.Он отлично работает.

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

В запросе используются следующие типы для группировки и суммирования.

    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"))

Я запрограммировал приведенный ниже код.Я отметил в комментариях, где я застрял.

    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 Ответ

0 голосов
/ 31 мая 2018

Я внес некоторые изменения в ваш код:

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, которая находит и обрабатывает универсальные методы, и вам не нужен массив / List<>чтобы вызвать Expression.Call, потому что у него перегрузка params.

Обратите внимание, что я изменил ваш код на Enumerable ... Я не думаю, что вы можете делать то, что вы хотите с Queryable ... Но вы можете попытаться изменить это обратно.Обратите внимание, что хотя вы пытались сделать код «универсальным» для типа AggN0 (PropertyInfo propertyInfo, который используется только для обнаружения типа AggN0), ключевое слово double появляется в местах, гдетрудно удалить (тип возврата метода GetFunc())

...