To dynamically generate a GroupBy expression, I am trying to build a Linq expression tree. The fields to group by are dynamic and can differ in number.
I use this code:
string[] fields = {"Name", "Test_Result"};
Type studentType = typeof(Student);
var itemParam = Expression.Parameter(studentType, "x");
var addMethod = typeof(Dictionary<string, object>).GetMethod(
"Add", new[] { typeof(string), typeof(object) });
var selector = Expression.ListInit(
Expression.New(typeof(Dictionary<string,object>)),
fields.Select(field => Expression.ElementInit(addMethod,
Expression.Constant(field),
Expression.Convert(
Expression.PropertyOrField(itemParam, field),
typeof(object)
)
)));
var lambda = Expression.Lambda<Func<Student, Dictionary<string,object>>>(
selector, itemParam);
The code is copied from this post (Thanks Mark Gravel!).
It finalizes with ...
var currentItemFields = students.Select(lambda.Compile());
... of which I expected that I could change it to ...
var currentItemFields = students.GroupBy(lambda.Compile());
I assumed that the lambda expression is nothing more than ...
var currentItemFields = students.GroupBy(o => new { o.Name, o.Test_Result });
... but unfortunally that seems not to be the case. The GroupBy with a dynamic lambda does not give any exceptions, it just doesn't group anything and returns all elements.
What am I doing wrong here? Any help would be appreciated. Thanks in advance.
This post shows a expression function which can be used for both Select and GroupBy. Hope it helps others!
To be called like this:
That lambda expression builds a dictionary of grouping fields.
Dictionary<TKey, TValue>
does not implementEquals()
andGetHashCode()
, so it groups them by reference equality.Since you always return a new dictionary, each item gets its own group.
You need to change it to create a type that correctly implements
Equals()
andGetHashCode()
for value equality.Ordinarily, you would have the compiler generate an anonymous type. However, you can't do that here since you don't know the type signature at compile-time.
Instead, you can construct a
Tuple<...>
: