Динамическое добавление GroupBy в лямбда-выражение

Хорошо, я признаю, что я еще не совсем "получил" лямбда-выражения и деревья выражений LINQ; многое из того, что я делаю, — это вырезание и вставка и просмотр того, что работает. Я просмотрел много документации, но так и не нашел своего момента "ага".

С учетом сказанного...

Я пытаюсь динамически добавить выражение GroupBy в выражение Linq. Я следил за вопросом здесь: Нужна помощь в создании Linq.Expression для Enumerable.GroupBy

и попытался реализовать то, что я там увидел.

Во-первых, у меня есть классы сущностей для моей базы данных и таблица с именем ObjCurLocViewNormalized.

У меня есть метод, который выполняет первоначальный вызов,

public IQueryable<ObjCurLocViewNormalized> getLocations()
{
    IQueryable<ObjCurLocViewNormalized> res = (from loc in tms.ObjCurLocViewNormalized
                               select loc);
    return res;
}

поэтому я могу позвонить:

IQueryable<MetAmericanLinqDataModel.ObjCurLocViewNormalized> locations = american.getLocations();

Пока нет проблем.

Теперь я хочу сгруппировать по произвольному столбцу с помощью такого вызова:

var grouped = locations.addGroupBy(childLocationFieldName);

Прямо сейчас у меня есть метод:

static public System.Linq.IQueryable<System.Linq.IGrouping<string, TResult>> addGroupBy<TResult>(this IQueryable<TResult> query, string columnName)
{

    var providerType = query.Provider.GetType();
    // Find the specific type parameter (the T in IQueryable<T>)
    var iqueryableT = providerType.FindInterfaces((ty, obj) => ty.IsGenericType && ty.GetGenericTypeDefinition() == typeof(IQueryable<>), null).FirstOrDefault();
    var tableType = iqueryableT.GetGenericArguments()[0];
    var tableName = tableType.Name;

    var data = Expression.Parameter(iqueryableT, "query");
    var arg = Expression.Parameter(tableType, tableName);
    var nameProperty = Expression.PropertyOrField(arg, columnName);
    var lambda = Expression.Lambda<Func<TResult, string>>(nameProperty, arg);

    var expression = Expression.Call(typeof(Enumerable), 
                                    "GroupBy", 
                                    new Type[] { tableType, typeof(string) },
                                    data, 
                                    lambda);
    var predicate = Expression.Lambda<Func<TResult, String>>(expression, arg); // this is the line that produces the error I describe below
    var result = query.GroupBy(predicate).AsQueryable();
    return result;
}

Все компилируется нормально, но при запуске получаю ошибку:

System.ArgumentException: Expression of type 'System.Collections.Generic.IEnumerable`1[System.Linq.IGrouping`2[System.String,MetAmericanLinqDataModel.ObjCurLocViewNormalized]]' cannot be used for return type 'System.String'

и ошибка исходит из этой строки:

 var predicate = Expression.Lambda<Func<TResult, String>>(expression, arg);

Я копирую и адаптирую этот код из успешной работы, которую я проделал в динамически добавленных предложениях Where к выражению. Так что здесь я как бы ковыряюсь в темноте.

Если кто-нибудь может помочь пролить свет на это, очевидно, опубликовать полный рабочий код и сделать все, что я думаю за меня, было бы здорово :), но если бы вы могли просто изложить, почему это неправильно, или как обернуть мою голову вокруг этих концепций, это было бы здорово. Если вы можете указать на документацию, которая действительно может помочь преодолеть разрыв между основами лямбда-выражений и построением динамических деревьев выражений, это было бы здорово. В моих знаниях явно есть большие пробелы, но я думаю, что эта информация может быть полезна другим.

спасибо всем за ваше время, и, конечно, если я найду ответ в другом месте, я опубликую его здесь.

Спасибо еще раз.

Дон


person donundeen    schedule 07.10.2010    source источник
comment
Почему ваша строка predicate использует Func<TResult, string>? Ваше выражение возвращает IGrouping, которое не является string.   -  person Kirk Woll    schedule 08.10.2010
comment
спасибо, кирк, я попробовал: изменить строку на: var predicate = Expression.Lambda‹Func‹TResult, IGrouping‹String, TResult›››(expression, arg); но теперь я получаю ошибку компиляции: Ошибка 5 Не удается неявно преобразовать тип «System.Linq.IQueryable», «System.Linq.IGrouping», «System.Linq.IGrouping», «строка, TResult», «TResult», «» в «System.Linq.IQueryable». System.Linq.IGrouping‹строка,TResult››». Существует явное преобразование (вам не хватает приведения?) . Это неправильный способ написать то, что вы мне говорите?   -  person donundeen    schedule 08.10.2010
comment
основная проблема заключается в том, что вы путаете вызов .GroupBy с лямбда-предикатом, который вы передаете it. Сейчас у меня нет времени отвечать дальше, но если этот вопрос все еще останется без ответа через час, я сделаю более тщательный удар.   -  person Kirk Woll    schedule 08.10.2010
comment
ах, и эта ошибка связана с несоответствием возвращаемого типа. Но я не думаю, что хочу изменить тип возвращаемого значения, не так ли?   -  person donundeen    schedule 08.10.2010


Ответы (2)


Решение должно быть довольно простым:

public static IQueryable<IGrouping<TColumn, T>> DynamicGroupBy<T, TColumn>(
    IQueryable<T> source, string column)
{
    PropertyInfo columnProperty = typeof(T).GetProperty(column);
    var sourceParm = Expression.Parameter(typeof(T), "x");
    var propertyReference = Expression.Property(sourceParm, columnProperty);
    var groupBySelector = Expression.Lambda<Func<T, TColumn>>(propertyReference, sourceParm);

    return source.GroupBy(groupBySelector);
}

Предполагая пример класса следующим образом:

public class TestClass
{
    public string TestProperty { get; set; }
}

Вы вызываете его так:

var list = new List<TestClass>();
var queryable = list.AsQueryable();
DynamicGroupBy<TestClass, string>(queryable, "TestProperty");
person Kirk Woll    schedule 08.10.2010
comment
Кажется, ваш ответ похож на мой, но теперь такой полный и с рефакторингом кода =) - person Restuta; 08.10.2010
comment
Хм, выглядит многообещающе, но нет перегрузки для метода «Параметр» принимает аргументы «1». Так что ему не нравится строка: var sourceParm = Expression.Parameter(typeof(T)); Я на 3,5. это вещь .Net 4.0? Я обновляюсь в ближайшее время. - person donundeen; 08.10.2010
comment
@donundeen, да, это вещь .Net 4, но это не имеет значения. Просто передайте в качестве второго параметра любую строку, которая, по вашему мнению, помогает идентифицировать параметр, или просто x. Это не имеет значения. :) - person Kirk Woll; 08.10.2010

Все, что вам нужно сделать, чтобы заставить его работать, это следующее:

    static public IQueryable<IGrouping<TValue, TResult>> addGroupBy<TValue, TResult>(
                this IQueryable<TResult> query, string columnName)
            {
            var providerType = query.Provider.GetType();

            // Find the specific type parameter (the T in IQueryable<T>)
            const object EmptyfilterCriteria = null;
            var iqueryableT = providerType
                .FindInterfaces((ty, obj) => ty.IsGenericType && ty.GetGenericTypeDefinition() == typeof(IQueryable<>), EmptyfilterCriteria)
                .FirstOrDefault();
            Type tableType = iqueryableT.GetGenericArguments()[0];
            string tableName = tableType.Name;

            ParameterExpression data = Expression.Parameter(iqueryableT, "query");
            ParameterExpression arg = Expression.Parameter(tableType, tableName);
            MemberExpression nameProperty = Expression.PropertyOrField(arg, columnName);
            Expression<Func<TResult, TValue>> lambda = Expression.Lambda<Func<TResult, TValue>>(nameProperty, arg);
            //here you already have delegate in the form of "TResult => TResult.columnName"
            return query.GroupBy(lambda);

        /*var expression = Expression.Call(typeof(Enumerable), 
                                        "GroupBy", 
                                        new Type[] { tableType, typeof(string) },
                                        data, 
                                        lambda);
        var predicate = Expression.Lambda<Func<TResult, String>>(expression, arg); // this is the line that produces the error I describe below
        var result = query.GroupBy(predicate).AsQueryable();
        return result;*/
    }

И вы будете называть свое выражение следующим образом:

var grouped = locations.addGroupBy<string, ObjCurLocViewNormalized>(childLocationFieldName);

Первый общий параметр «строка», используемый для указания того, какой тип элементов вы группируете. Например, вы можете сгруппировать по полю «int», и вызов метода будет выглядеть следующим образом:

var grouped = locations.addGroupBy<int, ObjCurLocViewNormalized>(someFieldNameWithTheTypeOfInt);

Изменить Просто чтобы завершить это решение по-вашему:

 //return query.GroupBy(lambda);
    MethodCallExpression expression = Expression.Call(typeof (Enumerable),
                                                      "GroupBy",
                                                      new[] { typeof(TResult), typeof(TValue) },
                                                      data,
                                                      lambda);

    var result = Expression.Lambda(expression, data).Compile().DynamicInvoke(query);
    return ((IEnumerable<IGrouping<TValue, TResult>>)result).AsQueryable();
person Restuta    schedule 07.10.2010
comment
почему у вас такой странный код после комментария // Find the specific type parameter (the T in IQueryable<T>)? Наверняка у вас уже есть эта информация в TResult, нет? - person Kirk Woll; 08.10.2010
comment
Я предпочитаю не трогать исходный код, потому что вопрос был о другом. Конечно, код можно улучшить. - person Restuta; 08.10.2010