1

I have a list of objects which need to be grouped by a set of properties. These set of properties would be known only at runtime . I would like to accomplish this without using Dynamic LINQ. Following code from Dynamically Adding a GroupBy to a Lambda Expression by Kirk Woll works fine, but this groups the result by one property.

 public static IQueryable<IGrouping<TColumn, T>> DynamicGroupBy<T, TColumn>(IQueryable<T> source, string column)
    {
        PropertyInfo columnProperty = typeof(T).GetProperty(column);
        var sourceParm = Expression.Parameter(typeof(T), "x");
        var propertyReference = Expression.Property(sourceParm, columnProperty);
        var groupBySelector = Expression.Lambda<Func<T, TColumn>>(propertyReference, sourceParm);

        return source.GroupBy(groupBySelector);

    }

I would like to pass a list of columnNames and be able to groupby multiple keySelectors. I am not sure how to accomplish this. Please help.

Community
  • 1
  • 1
ideafountain
  • 575
  • 5
  • 17
  • possible duplicate of [Dynamic LINQ GroupBy Multiple Columns](http://stackoverflow.com/questions/3929041/dynamic-linq-groupby-multiple-columns) – Gert Arnold Sep 11 '12 at 21:44

2 Answers2

1

It seems like if we could dynamically generate the equivalent of the expression: x => new { a = x.Prop1, b = x.Prop2, ... } Then a reasonable LINQ => SQL provider would probably generate the SQL you want. Generating a new anonymous type on the fly seems hard, but we could take advantage of the fact that anonymous types are generic to re-use one.

// at the top of your class
private static readonly object AnonymousObjectWithLotsOfProperties = new {
    a = 1,
    b = 2,
    ...
};

// then in your method
// (1) first get the list of properties represented by the string you passed in (I assume you know how to do this via reflection)
var props = typeof(T).GetProperties().Where(...);
var propTypes = props.Select(pi => pi.PropertyType).ToList();

// (2) now generate the correctly genericized anonymous type to use
var genericTupleType = AnonymousObjectWithLotsOfProperties.GetType()
    .GetGenericTypeDefinition();
// for generic args, use the prop types and pad with int
var genericArguments = propTypes.Concat(Enumerable.Repeat(typeof(int), genericTupleType.GetProperties().Length - propTypes.Count))
    .ToArray();
var tupleType = genericTupleType.MakeGenericType(genericArguments);

// (3) now we have to generate the x => new { ... }  expression
// if you inspect "System.Linq.Expressions.Expression<Func<object>> exp = () => new { a = 2, b = 3 };"
// in the VS debugger, you can see that this is actually a call to a constructor
// on the anonymous type with 1 argument per property
var tParameter = Expression.Parameter(typeof(T));
// create the expression
var newExpression = Expression.New(
    constructor: tupleType.GetConstructors().Single(), // get the one constructor we need
    // the arguments are member accesses on the parameter of type T, padded with 0
    arguments: props.Select(pi => Expression.MakeMemberAccess(tParameter, pi))
        .Concat(Enumerable.Repeat(Expression.Constant(0), genericTupleType.GetProperties().Length - propTypes.Count))
);
// create the lambda: we need an Expression<TDelegate>, which means that we
// need to get the generic factory method from Expression and invoke it
var lambdaGenericMethod = typeof(Expression).GetMethods(BindingFlags.Static | BindingFlags.Public)
    .Single(m => m.IsGenericMethodDefinition);
var lambdaMethod = lambdaGenericMethod.MakeGenericMethod(typeof(Func<,>).MakeGenericType(typeof(T), tupleType));
// reflection for Expression.Lambda(body, parameters)
var lambda = lambdaGenericMethod.Invoke(null, new object[] { newExpression, new[] { tParameter });

// now that we have the expression, we can invoke GroupBy via reflection.
// Of course, that leaves you with an IQueryable<IGrouping<ANON, T>>, which isn't much
// use until you apply some other IQueryable methods to eliminate the ANON type from the
// method signature so you can return it

Note that I didn't actually get to compile and run this code, so I can't promise that everything above is perfect. However, hopefully it will put you on the right track.

ChaseMedallion
  • 20,860
  • 17
  • 88
  • 152
  • Thanks Chase for your response. I was looking for a solution to create keyselector with anonymous types representing the group by columns. Your code gives me a good headstart. I get the error Late bound operations cannot be performed on types or methods for which ContainsGenericParameters is true in the lamdaGenericMethod.Invoke line .. I am trying to solve this right now ... Thanks – ideafountain Sep 12 '12 at 16:23
  • @ideafountain that error just means that you're trying to invoke a GenericMethodDefinition (e. g. Select<>() rather than Select()). Where in the code are you hitting this? – ChaseMedallion Sep 12 '12 at 18:32
0

What's wrong with this:

Group By Multiple Columns

Here's an example using the .GroupBy method rather than the Linq:

public class Program
{
    static void Main(string[] args)
    {

        var list = new List<SomeType>
            {
                new SomeType("Mr", "Joe", "Bloggs"),
                new SomeType("Mr", "John", "Citizen"),
                new SomeType("Mrs", "Mary", "Mac")
            };

        var list2 = list.GroupBy(by => new { by.Property1, by.Property2 });
    }
}

public class SomeType
{
    public string Property1 { get; private set; }
    public string Property2 { get; private set; }
    public string Property3 { get; private set; }

    public SomeType(string property1, string property2, string property3)
    {
        Property1 = property1;
        Property2 = property2;
        Property3 = property3;
    }
}
Community
  • 1
  • 1
Tod Thomson
  • 4,773
  • 2
  • 33
  • 33