3

I have a Group by expression that I am dynamically creating for use in a LINQ query. Currently, to construct the expression, I use the following code:

var arg = Expression.Parameter(typeof(T), helper.getName());
var prop = Expression.Property(arg, "customerType");
var body = Expression.Convert(prop, typeof(object));
var lambda = Expression.Lambda<Func<Contact, object>>(body, arg);
var keySelector = lambda.Compile();

I then use the keySelector in the GroupBy for my LINQ query. My question is, if I wanted to add a second grouping criteria to this expression, say "salesStage", how would I add that to this existing expression?

Dude0001
  • 3,019
  • 2
  • 23
  • 38
mjsalinger
  • 660
  • 6
  • 17
  • If I understand your question correctly, you're trying to create an expression tree. [Take a look at this](http://stackoverflow.com/a/457328/1082327). – Arian Motamedi Sep 04 '13 at 18:24
  • I've used that for Where expressions, but am unsure how to build multiple UnaryExpressions into an expressionTree that are needed for GroupBy. – mjsalinger Sep 04 '13 at 19:06

1 Answers1

0

You have a problem, because what the compiler does on a regular GroupBy call is generate a new anonymous type with the properties you define. If the type doesn't exist, we cannot create an expression creating an object of the type.

However, given that you are using this for LINQ-to-Objects, we can use the Tuple<> type to generate the grouping key. Hopefully you do not need to group on more than 8 parameters.

Here is a generic function to generate the grouping function:

static Func<T, object> BuildGrouper<T>(IEnumerable<string> properties) {
    var arg = Expression.Parameter(typeof(T), helper.getName());
    // This is the list of property accesses we will be using
    var parameters = properties.Select(propName => Expression.Property(arg, propName)).ToList();
    // Find the correct overload of Tuple.Create.
    // This will throw if the number of parameters is more than 8!
    var method = typeof(Tuple).GetMethods().Where(m => m.Name == "Create" && m.GetParameters().Length == parameters.Count).Single();
    // But it is a generic method, we need to specify the types of each of the arguments
    var paramTypes = parameters.Select(p => p.Type).ToArray();
    method = method.MakeGenericMethod(paramTypes);
    // Invoke the Tuple.Create method and return the Func
    var call = Expression.Call(null, method, parameters);
    var lambda = Expression.Lambda<Func<T, object>>(call, arg);
    return lambda.Compile();
}
felipe
  • 662
  • 4
  • 16