【问题标题】:Refactoring Func<T> into Expression<Func<T>>将 Func<T> 重构为 Expression<Func<T>>
【发布时间】:2009-10-20 16:49:06
【问题描述】:

我有一个当前将Func&lt;Product, string&gt; 作为参数的方法,但我需要它是Expression&lt;Func&lt;Product, string&gt;&gt;。使用 AdventureWorks,这是我想使用 Func 做的一个示例。

private static void DoSomethingWithFunc(Func<Product, string> myFunc)
{
    using (AdventureWorksDataContext db = new AdventureWorksDataContext())
    {
        var result = db.Products.GroupBy(product => new
        {
            SubCategoryName = myFunc(product),
            ProductNumber = product.ProductNumber
        });
    }
}

我希望它看起来像这样:

private static void DoSomethingWithExpression(Expression<Func<Product, string>> myExpression)
{
    using (AdventureWorksDataContext db = new AdventureWorksDataContext())
    {
        var result = db.Products.GroupBy(product => new
            {
                SubCategoryName = myExpression(product),
                ProductNumber = product.ProductNumber
            });
    }
}

但是,我遇到的问题是 myExpression(product) 无效(无法编译)。在阅读了其他一些帖子后,我明白了为什么。如果不是因为我的密钥的第二部分需要product 变量,我可能会这样说:

var result = db.Products.GroupBy(myExpression);

但我确实需要product 变量,因为我确实需要密钥的第二部分(ProductNumber)。所以我不确定现在该怎么做。我不能将其保留为 Func,因为这会导致问题。我不知道如何使用表达式,因为我看不到如何将product 变量传递给它。有什么想法吗?

编辑:这是我如何调用该方法的示例:

DoSomethingWithFunc(product => product.ProductSubcategory.Name);

【问题讨论】:

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


    【解决方案1】:

    无法将表示为Expression&lt;T&gt; 对象的表达式树拼接到由 lambda 表达式表示的“树文字”的中间。您必须手动构建一个表达式树以传递给GroupBy:

    // Need an explicitly named type to reference in typeof()
    private class ResultType
    {
         public string SubcategoryName { get; set; }
         public int ProductNumber { get; set; }|
    }
    
    private static void DoSomethingWithExpression(
        Expression<Func<Product,
        string>> myExpression)
    {
        var productParam = Expression.Parameter(typeof(Product), "product");
        var groupExpr = (Expression<Func<Product, ResultType>>)Expression.Lambda(
            Expression.MemberInit(
               Expression.New(typeof(ResultType)),
               Expression.Bind(
                   typeof(ResultType).GetProperty("SubcategoryName"),
                   Expression.Invoke(myExpression, productParam)),
               Expression.Bind(
                   typeof(ResultType).GetProperty("ProductNumber"),
                   Expression.Property(productParam, "ProductNumber"))),
            productParam);
        using (AdventureWorksDataContext db = new AdventureWorksDataContext())
        {
            var result = db.Products.GroupBy(groupExpr);
        }
    }
    

    【讨论】:

    • 不错!但是,最后一行没有为我编译,结果被分配到。 “方法的类型参数......无法从用法中推断出来。”我错过了什么吗?
    • Expression.Lambda 的返回值应该转换为Expression&lt;Func&lt;Product, ResultType&gt;&gt;。
    • 太棒了!事实证明,这比我预期的要复杂。我以前从来没有做过这样的自己的表达,所以我将研究这段代码,以确保我完全理解发生了什么。谢谢!
    • 是的——学习如何手动构建表达式树永远不会太早;有许多常见的场景几乎都需要它......
    • 看起来你也可以说 Expression.Lambda>(... 而不是 cast.
    【解决方案2】:

    再想一想,编译表达式是行不通的。

    您需要手动构建 GroupBy 表达式,这意味着您不能使用匿名类型。我建议构建表达式的其余部分,然后反编译以查看生成的表达式树。最终结果将如下所示,酌情使用myExpression 的部分内容:

    private static void DoSomethingWithExpression(Expression<Func<Product, string>> myExpression)
    {
        var productParam = myExpression.Parameters[0];
    
        ConstructorInfo constructor = ...; // Get c'tor for return type
    
        var keySelector = Expression.Lambda(
                              Expression.New(constructor,
                                  new Expression[] {
                                      productParam.Body,
                                      ... // Expressions to init other members
                                  },
                                  new MethodInfo[] { ... }), // Setters for your members
                              new [] { productParam });
    
        using (AdventureWorksDataContext db = new AdventureWorksDataContext())
        {
            var result = db.Products.GroupBy(keySelector);
    
            // ...
        }
    }
    

    【讨论】:

    • 您首先错过了他从 Func 移动到 Expression 的原因 - 他希望这在 LINQ to SQL 上下文中工作,并且不允许随机函数(或委托) 在查询中调用。
    • 你击败了 Pavel,但他的回答对我来说更清楚。不过,+1 为您提供帮助。谢谢!
    • 同意 Pavel 的回答更好。我的代码基于为构造匿名类型而生成的表达式; MemberInit 是您想要的命名类型。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-10-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多