【问题标题】:Expression.Call groupBy then Select?Expression.Call groupBy 然后选择?
【发布时间】:2014-05-28 02:38:05
【问题描述】:

我正在尝试使用表达式树来构建一对嵌套的组,并且完全被 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. 在这种情况下,正确的类型/参数是什么?

【问题讨论】:

    标签: c# .net linq linq-to-objects expression-trees


    【解决方案1】:

    以下是执行您想要的操作的代码。每个选择都需要在表达式树中有自己的 lamda 投影。您还有两种不同的匿名类型,一种是内部匿名类型的 IEnumerable,一种是产品列表。

    此外,由于它是对不需要 Queryable 的对象的 linq,因此您可以只使用 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);
    

    【讨论】:

    • 太棒了,谢谢!你如何弄清楚这些都应该是什么?换句话说,虽然这很好用,但如果没有示例,我应该如何将其拼凑起来才能弄清楚呢?
    • 我已经做了很多表达式树。 :) 我从阅读博客和实体框架中学到了东西。 IQueryable 接口有一个 Expression 属性,用于检索表达式树,并且通过调试视图,您可以了解它在做什么。为了了解我已经构建了一个 linq 语句,就像您对 Linq to Objects 所做的那样,但是使用 EF,我可以检查它构建的表达式树作为示例。你真的不能用 linq to objects 做到这一点,但 Enumerable 和 Queryable 类之间的方法签名是相同的。表达式树不适合胆小的人:)
    猜你喜欢
    • 1970-01-01
    • 2019-02-22
    • 2021-06-18
    • 1970-01-01
    • 2020-12-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多