views:

313

answers:

2

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);
+3  A: 

On second thought, compiling the expression won't work.

You'll need to build your GroupBy expression by hand, which means you can't use anonymous type. I would suggest building out the rest of your expression and then decompiling to see the generated expression tree. The end result will look something like this, using parts of myExpression as appropriate:

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

        // ...
    }
}
dahlbyk
You missed the reason why he's moving from `Func` to `Expression` in the first place - he wants this to work in LINQ to SQL context, and that won't permit random function (or delegate) calls in a query.
Pavel Minaev
Yeah I see that now - working on an Edit.
dahlbyk
You beat Pavel to the punch but his answer was more clear to me. +1 for your help, though. Thanks!
Ecyrb
Agreed that Pavel's answer is better. I based my code on the expression generated for constructing an anonymous type; a MemberInit is what you want for a named type.
dahlbyk
+3  A: 

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);
    }
}
Pavel Minaev
Nice! The last line isn't compiling for me, though, where result is assigned to. "The type arguments for method ... cannot be inferred from the usage." Am I missing something?
Ecyrb
The return value from `Expression.Lambda` should be cast to `Expression<Func<Product, ResultType>>`.
Ben M
Excellent! This turned out to more complex than I was expecting. I've never made my own Expression like that before, so I'll be studying this code to make sure I fully understand what's going on. Thanks!
Ecyrb
Yeah--it's never too soon to learn how to manually build expression trees; there are many common scenarios that all but demand it...
Ben M
Thanks for pointing that out Ben, fixed.
Pavel Minaev
Looks like you could also say Expression.Lambda<Func<Product, ResultType>>(...rather than casting.
Ecyrb