将Func <t>重构为表达式<func <t>&gt; </func <t> </t>

时间:2009-10-20 16:49:06

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

我有一个方法,当前需要Func<Product, string>作为参数,但我需要它是Expression<Func<Product, string>>。使用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,因为这会导致问题。我无法弄清楚如何使用Expression,因为我看不出如何将product变量传递给它。有什么想法吗?

编辑:以下是我将如何调用该方法的示例:

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

2 个答案:

答案 0 :(得分:4)

无法将表示为Expression<T>对象的表达式树拼接到由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);
    }
}

答案 1 :(得分:3)

第二个想法,编译表达式是行不通的。

您需要手动构建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);

        // ...
    }
}