Search code examples
c#.netlinq-to-sqlexpression-trees

Refactoring Func<T> into Expression<Func<T>>


I have a method that currently takes a Func<Product, string> as a parameter, but I need it to be an Expression<Func<Product, string>>. Using AdventureWorks, here's an example of what I'd like to do using the 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
        });
    }
}

I would like it to look something like this:

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
            });
    }
}

However, the problem I'm running into is that myExpression(product) is invalid (won't compile). After reading some other posts I understand why. And if it wasn't for the fact that I need the product variable for the second part of my key I could probably say something like this:

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

But I do need the product variable because I do need the second part of the key (ProductNumber). So I'm not really sure what to do now. I can't leave it as a Func because that causes problems. I can't figure out how to use an Expression because I don't see how I could pass it the product variable. Any ideas?

EDIT: Here's an example of how I would call the method:

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

Solution

  • There's no way to splice an expression tree that is represented as an Expression<T> object into a middle of a "tree literal" represented by lambda expression. You'll have to construct an expression tree to pass to GroupBy manually:

    // 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);
        }
    }