Выражение. Вызовите группу, затем выберите?

1

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

var queryNestedGroups = products.GroupBy(x => x.Category)
                .Select(p => new
                {
                    key = p.Key,
                    objects = p.ToList().GroupBy(y => y.Subcategory)
                        .Select(y => new { key = y.Key, objects = y.ToList() })
                })
                .AsQueryable();

Это моя попытка до сих пор (продукты - это список);

var data = Expression.Constant(products);
var arg = Expression.Parameter(typeof(Product), "arg");
var nameProperty = Expression.PropertyOrField(arg, "Category");

var groupByLambda = Expression.Lambda<Func<Product, string>>(nameProperty, arg);
var groupByExpression = Expression.Call(
    typeof(Queryable),
    "GroupBy",
    new Type[] { typeof(Product), typeof(string) },
    data,
    groupByLambda);

var parameterExp = Expression.Parameter(typeof(IGrouping<string, Product>), "p");
var keyProp = Expression.PropertyOrField(parameterExp, "Key");
ConstructorInfo constructorInfo = typeof(object)
    .GetConstructor(new[] { typeof(string), typeof(Product) });

Type anonymousResultType = new { Key = "abc", Values = new List<Product>() }.GetType();
var exp = Expression.New(
            anonymousResultType.GetConstructor(new[] { typeof(string), typeof(List<Product>) }),
            Expression.Constant("def"),
            Expression.Constant(new List<Product>()));
var selectLambda = Expression.Lambda(exp);

var selectExpression = Expression.Call(
    typeof(Queryable),
    "Select",
    new Type[] { typeof(List<Product>), selectLambda.Body.Type },
    data,
    selectLambda); 

var finalExpression = Expression.Lambda(groupByExpression);

Все шло хорошо, за исключением того, что я получаю исключения из var selectExpression =... говоря, что мои параметры и параметры типа неправильны. К сожалению, это не говорит мне, какие параметры и почему они ошибаются. Я пробовал каждую перестановку, о которой я могу думать здесь. Так что два вопроса;

Как я могу понять, что

  1. точно он хочет для типов?
  2. Каковы правильные типы/параметры в этом случае?
Теги:
linq
expression-trees
linq-to-objects

1 ответ

3
Лучший ответ

Ниже приведен код, который вы хотите сделать. Каждый выбор должен иметь свою собственную проекцию lamda в Expression Trees. У вас также есть два разных анонимных типа: IEnumerable внутреннего анонимного типа, а один - список продуктов.

Кроме того, поскольку linq для объектов вам не нужен Queryable, вы можете просто использовать Enumerable и p.ToList(). GroupBy (y => y.Subcategory) ToList не нужен, поэтому я не преобразовал его.

Также было бы проще, если бы вы не использовали анонимные типы и не имели конкретных классов. Особенно в конце. Поскольку он не может быть строго типизирован, вам просто придется его скомпилировать, а затем DynamicInvoke.

// This could be a parameter
var data = Expression.Constant(products);

var outterGroupByarg = Expression.Parameter(typeof(Product), "x");
var outterGroupNameProperty = Expression.PropertyOrField(outterGroupByarg, "Category");
var outterGroupByLambda = Expression.Lambda<Func<Product, string>>(outterGroupNameProperty, outterGroupByarg);
var outterGroupByExpression = Expression.Call(typeof(Enumerable), "GroupBy", new [] { typeof(Product), typeof(string) },
                                         data, outterGroupByLambda);

var outterSelectParam = Expression.Parameter(typeof (IGrouping<string, Product>), "p");

var innerGroupByarg = Expression.Parameter(typeof(Product), "y");
var innerGroupNameProperty = Expression.PropertyOrField(innerGroupByarg, "Subcategory");
var innerGroupByLambda = Expression.Lambda<Func<Product, string>>(innerGroupNameProperty, innerGroupByarg);

var innerGroupByExpression = Expression.Call(typeof(Enumerable), "GroupBy", new[] { typeof(Product), typeof(string) },
                                         outterSelectParam, innerGroupByLambda);

var innerAnonymousType = new {Key = "abc", objects = new List<Product>()};

var innerSelectProjectionarg = Expression.Parameter(typeof(IGrouping<string, Product>), "y");
var innerKeyProp = Expression.Property(innerSelectProjectionarg, "Key");

var innerToList = Expression.Call(typeof (Enumerable), "ToList", new[] {typeof (Product)},
                                  innerSelectProjectionarg);

var innerAnonymousResultType = innerAnonymousType.GetType();
var innerAnonymousConstructor =
    innerAnonymousResultType.GetConstructor(new[] {typeof (string), typeof (List<Product>)});
var innerAnonymous = Expression.New(innerAnonymousConstructor, innerKeyProp, innerToList);

var innerSelectProjection =
    Expression.Lambda(typeof(Func<,>).MakeGenericType(typeof(IGrouping<string, Product>), innerAnonymousResultType), innerAnonymous,
                      innerSelectProjectionarg);

var innerSelectExpression = Expression.Call(typeof(Enumerable), "Select", new [] { typeof(IGrouping<string, Product>), innerAnonymousResultType },
                            innerGroupByExpression, innerSelectProjection);


var outterAnonymousType = new {Key = "abc", Values = new[] {innerAnonymousType}.AsEnumerable()};
var outterAnonymousResultType = outterAnonymousType.GetType();
var outterAnonymousConstructor =
    outterAnonymousResultType.GetConstructor(new[] { typeof(string), typeof(IEnumerable<>).MakeGenericType(innerAnonymousResultType) });

var outterKeyProp = Expression.PropertyOrField(outterSelectParam, "Key");
var outterAnonymous = Expression.New(outterAnonymousConstructor, outterKeyProp, innerSelectExpression);
var outterSelectProjection =
    Expression.Lambda(
        typeof (Func<,>).MakeGenericType(typeof (IGrouping<string, Product>), outterAnonymousResultType),
        outterAnonymous,
        outterSelectParam);

var outterSelect = Expression.Call(typeof(Enumerable), "Select", new[] { typeof(IGrouping<string, Product>), outterAnonymousResultType },
                                  outterGroupByExpression, outterSelectProjection);

// Lamda is a func with no input because list of products was set as a constant and not a parameter
var finial =
    Expression.Lambda(
        typeof (Func<>).MakeGenericType(typeof (IEnumerable<>).MakeGenericType(outterAnonymousResultType)),
        outterSelect);
  • 0
    Круто, спасибо! Как вы понимаете, что все это должно быть? Другими словами, хотя это прекрасно работает, как мне собрать это воедино, чтобы понять это без примера?
  • 0
    Я сделал много Деревьев Выражений. :) Я узнал, читая блоги, а также Entity Framework. Интерфейс IQueryable <> имеет свойство Expression, которое извлекает дерево выражений, и с помощью представления отладки вы можете разобрать, что он делает. Чтобы узнать, я построил оператор linq, как то, что вы сделали с Linq to Objects, но с EF я могу исследовать деревья выражений, которые он построил в качестве примера. Вы действительно не можете сделать это с linq для объектов, но сигнатуры методов одинаковы для классов Enumerable и Queryable. Деревья выражения не для слабонервных :)

Ещё вопросы

Сообщество Overcoder
Наверх
Меню