2014-01-15 5 views
5

Um einen GroupBy-Ausdruck dynamisch zu generieren, versuche ich einen Linq-Ausdrucksbaum zu erstellen. Die Felder für die Gruppierung sind dynamisch und können sich in der Anzahl unterscheiden.Build GroupBy Ausdrucksbaum mit mehreren Feldern

Ich benutze diesen Code: (! Danke Mark Gravel)

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

Der Code von this post kopiert wird.

Es schließt mit ...

var currentItemFields = students.Select(lambda.Compile()); 

... von denen erwartete ich, dass ich es ändern könnte ...

var currentItemFields = students.GroupBy(lambda.Compile()); 

ich davon ausgegangen, dass der Lambda-Ausdruck ist nicht mehr als ...

var currentItemFields = students.GroupBy(o => new { o.Name, o.Test_Result }); 

... aber unfortunally das nicht der Fall zu sein scheint. Die GroupBy mit einem dynamischen Lambda gibt keine Ausnahmen, es gruppiert einfach nichts und gibt alle Elemente zurück.

Was mache ich hier falsch? Jede Hilfe wäre willkommen. Danke im Voraus.

+0

Wenn Sie aus dem erzeugten Ausdruck drucken, bevor es kompilieren, wie sieht es aus? – Servy

+0

@Servy So: {x => neues Dictionary'2() {Void Add (System.String, System.Object) ("Shift", Konvertieren (x.Shift)), Void Add (System.String, System. Object) ("Section", Convert (x.Section))}} –

+0

Es sollte also nicht offensichtlich sein, dass das Endergebnis ein Wörterbuch ist, und Sie wissen, dass die Gruppierung in einem Wörterbuch Vergleiche basierend auf Die Referenz des Wörterbuchs, nicht sein Inhalt, macht die Ergebnisse klar. – Servy

Antwort

3

Dieser Lambda-Ausdruck erstellt ein Wörterbuch der Gruppierung von Feldern.
Dictionary<TKey, TValue> implementiert Equals() und GetHashCode() nicht, so dass sie sie durch Referenzgleichheit gruppiert.
Da Sie immer ein neues Wörterbuch zurückgeben, erhält jedes Element seine eigene Gruppe.

Sie müssen es ändern, um einen Typ zu erstellen, der Equals() und GetHashCode() für Wertgleichheit korrekt implementiert.
Normalerweise müsste der Compiler einen anonymen Typ generieren. Dies ist jedoch nicht möglich, da Sie die Typ-Signatur zum Zeitpunkt der Kompilierung nicht kennen.
Stattdessen können Sie ein Tuple<...> konstruieren:

Expression.New(
    Type.GetType("System.Tuple`" + fields.Length) 
     .MakeGenericType(fields.Select(studentType.GetProperty), 
    fields.Select(f => Expression.PropertyOrField(itemParam, f)) 
) 
+0

Danke für Ihre nützliche Antwort, es hat mir geholfen, eine Lösung zu finden, wie ich in einer separaten Antwort geschrieben habe. Vielleicht möchten Sie wissen, dass der von Ihnen geschriebene Beispielcode keine Klammer enthält und einen Konvertierungsfehler ergibt. –

4

This post einen Ausdruck Funktion zeigt, die sowohl für Select und GroupBy verwendet werden kann. Hoffe es hilft anderen!

public Expression<Func<TItem, object>> GroupByExpression<TItem>(string[] propertyNames) 
{ 
    var properties = propertyNames.Select(name => typeof(TItem).GetProperty(name)).ToArray(); 
    var propertyTypes = properties.Select(p => p.PropertyType).ToArray(); 
    var tupleTypeDefinition = typeof(Tuple).Assembly.GetType("System.Tuple`" + properties.Length); 
    var tupleType = tupleTypeDefinition.MakeGenericType(propertyTypes); 
    var constructor = tupleType.GetConstructor(propertyTypes); 
    var param = Expression.Parameter(typeof(TItem), "item"); 
    var body = Expression.New(constructor, properties.Select(p => Expression.Property(param, p))); 
    var expr = Expression.Lambda<Func<TItem, object>>(body, param); 
    return expr; 
} 

Um wie folgt aufgerufen werden:

var lambda = GroupByExpression<Student>(fields); 
var currentItemFields = students.GroupBy(lambda.Compile()); 
+0

Wie kann ich die Schlüssel für die weitere Auswahl nach der Gruppe identifizieren? Vielen Dank. –

+0

Ich habe das nicht versucht, aber warum sollte der flüssige Weg nicht funktionieren? students.GroupBy (lambda1.Compile()) Wählen Sie (lambda2.Compile()). Die Expression-Funktion kann für beide arbeiten GroupBy und [Select] (http://stackoverflow.com/questions/9000753/how-can-i-create-a-dynamic-multi-property-select-on-an-ienumerablet- at-runtime/9001249 # 9001249). –