views:

236

answers:

2

I've tried to simplify this example, as the actual code I'm playing with is more complex. So while this example may seem silly, bear with me. Let's say I'm working with the AdventureWorks database and I decide I want to add a property called Blarg to the Product table that returns an expression that contains code I would like to use in several places:

public partial class Product
{
    public Expression<Func<Product, string>> Blarg
    {
        get { return product => product.ProductModelID.HasValue ? "Blarg?" : "Blarg!"; }
    }
}

What I want to do is create an expression expression tree, have it get the Expression from Product.Blarg, and group by the result. Something like this:

var productParameter = Expression.Parameter(typeof(Product), "product");

// The Problem
var groupExpression = Expression.Lambda<Func<Product, string>>(
    Expression.Invoke(
        Expression.Property(productParameter, "Blarg"), 
        productParameter), 
    productParameter);

using (AdventureWorksDataContext db = new AdventureWorksDataContext())
{
    var result = db.Products.GroupBy(groupExpression).ToList();
    // Throws ArgumentException: "The argument 'value' was the wrong type. 
    //  Expected 'System.Delegate'. 
    //  Actual 'System.Linq.Expressions.Expression`1[System.Func`2[LINQ_Test.Product,System.String]]'."
}

Obviously groupExpression is incorrect (see the code comment for the exception), but I'm not sure how I should be doing it. What I thought I was saying is "get the Expression from the product.Blarg, execute it, and return the string result." I guess that's not what I'm actually saying there, though. I'm still trying to figure out expression trees. Any idea how I could pull this off?

+3  A: 

When you call Expression.Invoke, the first argument must be an existing LambdaExpression - it can't be an Expression to a LambdaExpression. Or in other words: it isn't going to evaluate Product.Blarg per row and use a different sub-expression each time.

Instead, you would retrieve this lambda first, perhaps making it static and accessing it via reflection if you only know it by name:

var lambda = (LambdaExpression) typeof(Product)
          .GetProperty("Blarg").GetValue(null,null);

And pass lambda in as the argument to Expression.Invoke; here's a fully working LINQ-to-Objects example showing this (via AsQueryable()):

using System;
using System.Linq;
using System.Linq.Expressions;
public partial class Product
{
    public static Expression<Func<Product, string>> Blarg
    {
        get { return product => product.ProductModelID.HasValue ? "Blarg?" : "Blarg!"; }
    }
    public int? ProductModelID { get; set; }

    static void Main()
    {
        var lambda = (LambdaExpression)typeof(Product)
          .GetProperty("Blarg").GetValue(null, null);

        var productParameter = Expression.Parameter(typeof(Product), "product");

        // The Problem
        var groupExpression = Expression.Lambda<Func<Product, string>>(
            Expression.Invoke(
                lambda,
                productParameter),
            productParameter);

        var data = new[] {
            new Product { ProductModelID = 123},
            new Product { ProductModelID = null},
            new Product { ProductModelID = 456},
        };
        var qry = data.AsQueryable().GroupBy(groupExpression).ToList();
    }
}
Marc Gravell
Something looks fishy with that sample code (unless LambdaExpression has some funny cast mechanism).
leppie
It does work, doh! I was not thinking :)
leppie
@marc: any reason why you are going this long route, when my solution above is sufficient?
leppie
Perfect! Thanks Marc!
Ecyrb
@leppie Your solution would work if I didn't need groupExpression. But, like I said, in my actual code things are more complex. So I needed to get the expression into groupExpression.
Ecyrb
+3  A: 
var qry = data.AsQueryable().GroupBy(Blarg).ToList();

That works, same as Marc's code.

Note: Blarg is already correct, there is no reason to 're-invoke' it.

leppie
You are correct ;-p I was thinking of the more general case, where you want to do other voodoo with the `Expression`. If you are already messing in that area, chances are you want to do something funky.
Marc Gravell