Komplexe LINQ-Sortierung mit Lambda-Expressions

.net c# expression-trees linq

Frage

Kennt jemand eine Erweiterung von IQueryable.OrderBy, die einen Ausdruck (z. B. von Reflection abgerufen) verwendet? Ich glaube, die Funktion würde ungefähr so ​​aussehen:

public static IQueryable<TEntity> OrderBy<TEntity>
    (this IQueryable<TEntity> source, Expression sortExpression)

Der Ausdruck würde als ein Expression<Func<TEntity, T>> wobei TEntität das gleiche Objekt ist, nach dem sortiert wird, und T ist ein Typ, der bestimmt werden muss, um das neue IQueryable zu erzeugen.

Ich habe viele Beispiele für Erweiterungen gefunden, die eine Zeichenfolge wie Dynamic Linq verwenden:

public static IQueryable<TEntity> OrderBy<TEntity>(
    this IQueryable<TEntity> source, string sortExpression) 

Wenn es möglich ist, den String zu nehmen und Reflection zu verwenden, um den Typ von dem fraglichen Objekt nachzuschlagen, sollte es auch möglich sein, den Ausdruck zu nehmen und den Werttyp zu erhalten, der genau dort im Ausdruck ist.


Im Folgenden finden Sie eine detaillierte Erklärung, warum ich diese haben möchte, die Sie brauchen oder nicht brauchen.

Ich habe eine ziemlich große Liste von komplexen Aufzeichnungen zu sortieren. Da die Liste so lang ist, bevorzuge ich die Sortierung auf der Datenbankseite. Um komplexere Eigenschaften zu behandeln, habe ich Ausdrücke erstellt, die die Sortierfunktionalität bieten:

if (model.sortExpression == "PlannedValue")
{
    Expression<Func<BDopp, decimal>> sorter = BDopp.PlannedValueSorter;         
    if (model.sortDirection == "DESC")
        opps = opps.OrderByDescending(sorter).AsQueryable();
    else
        opps = opps.OrderBy(sorter).AsQueryable();
}

BDOpp.PlannedValueSorter ruft einen statischen Ausdruck vom Objekt ab, der das Sortieren ermöglicht, ohne dass opps vom Typ IQueryable sind:

public static Expression<Func<BDopp, decimal>> PlannedValueSorter
    {
        get
        {
            return z => z.BudgetSchedules
                .Where(s => s.Type == 1)
                .Sum(s => s.Value * s.Workshare * z.valueFactor / 100 / 100);
        }
    }

Die Sortierung nach einfachen Eigenschaften erfolgt mit Extension-Methoden, die Reflection zum Erstellen eines Ausdrucks verwenden, der auf dem Namen der als Zeichenfolge übergebenen Eigenschaft basiert.

Das funktioniert gut, aber für die komplexen Typen brauche ich immer noch Verzweigungslogik, und das würde ich lieber nicht tun. Ich würde lieber nach einer statischen Eigenschaft suchen, die den Ausdruck enthält, und sie dann einfach anwenden. Ich kann den Ausdruck so bekommen:

PropertyInfo info = typeof(BDopp).GetProperty(model.sortExpression + "Sorter",
    BindingFlags.Static | BindingFlags.Public);
Expression expr = (Expression)info.GetValue(null, null);

Für die PlannedValue-Eigenschaft erhält dies den Ausdruck in PlannedValueSorter sortiert, von dem ich bereits weiß, dass er funktioniert.


Aktualisieren:

Verschiedene Umgrabungen haben mir geholfen, was ich denke, könnte ein Fortschritt sein:

public static IQueryable<TEntity> OrderBy<TEntity>(this IQueryable<TEntity> source, 
    Expression<Func<TEntity, dynamic>> sortExpression)
    {
        var unary = sortExpression.Body as UnaryExpression;
        Type actualExpressionType = unary.Operand.Type;

actualExpressionType ist in der Tat der Rückgabetyp des Ausdrucks (für diese bestimmte Eigenschaft ist es dezimal).

Leider arbeite ich meistens nur mit Versuch und Irrtum, da ich mich noch nicht damit beschäftigt habe, wie das alles funktioniert, weshalb mein Versuch, die Abfrage zu aktualisieren, nicht funktioniert:

        MethodCallExpression resultExp = Expression.Call(typeof(Queryable), 
            "OrderBy",
            new Type[] { typeof(TEntity), actualExpressionType },
            source.Expression, sortExpression);
        return source.Provider.CreateQuery<TEntity>(resultExp);

Es kompiliert in Ordnung, aber der folgende Fehler wird zur Laufzeit ausgelöst:

Keine generische Methode 'OrderBy' vom Typ 'System.Linq.Queryable' ist mit den angegebenen Typargumenten und Argumenten kompatibel. Wenn die Methode nicht generisch ist, sollten keine Typargumente angegeben werden.

Beliebte Antwort

Okay, ich habe eine Lösung:

public static IQueryable<TEntity> OrderBy<TEntity>(
    this IQueryable<TEntity> source, 
    Expression<Func<TEntity, dynamic>> sortExpression, 
    bool descending)
    {
        var unary = sortExpression.Body as UnaryExpression;
        var operand = unary.Operand;
        Type actualExpressionType = operand.Type;

        MethodCallExpression resultExp = 
            Expression.Call(typeof(Queryable), 
                descending? "OrderByDescending" : "OrderBy",
                new Type[] { typeof(TEntity), actualExpressionType },
                source.Expression, 
                Expression.Lambda(operand, sortExpression.Parameters));
        return source.Provider.CreateQuery<TEntity>(resultExp);
    }

Der Bool-Abstieg soll die Standard-OrderBy- und OrderByDescending-Überladungen berücksichtigen. Zwei wichtige Durchbrüche hier, zumindest für mich:

  1. Den Operanden aus dem Ausdruck entfernen.
  2. Verwenden von Expression.Call und Expression.Lambda zum Erstellen des neuen Ausdrucks - dies ermöglicht mir die Verwendung einer tatsächlichen "Type" Expression<Func<TEntity, T>> , während die Expression<Func<TEntity, T>> dass Sie einen Typ verwenden, der zur Kompilierungszeit bekannt ist.


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