Erstellen Sie GroupBy-Ausdrucksbaum mit mehreren Feldern

c# expression-trees lambda linq

Frage

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.

Ich benutze diesen 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);

Der Code wird von diesem Beitrag kopiert (Danke Mark Gravel!).

Es schließt mit ... ab

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

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

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

Ich nahm an, dass der Lambda-Ausdruck nichts anderes ist als ...

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

... aber leider scheint das nicht der Fall zu sein. 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.

Akzeptierte Antwort

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

Sie müssen es ändern, um einen Typ zu erstellen, der Equals() und GetHashCode() für die GetHashCode() korrekt implementiert.
Normalerweise würde 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))
)

Beliebte Antwort

Dieser Beitrag zeigt eine Ausdrucksfunktion, die sowohl für Select als auch für 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;
}  

So genannt werden:

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


Lizenziert unter: CC-BY-SA with attribution
Nicht verbunden mit Stack Overflow
Ist diese KB legal? Ja, lerne warum
Lizenziert unter: CC-BY-SA with attribution
Nicht verbunden mit Stack Overflow
Ist diese KB legal? Ja, lerne warum