C # - Code zum Bestellen nach einer Eigenschaft unter Verwendung des Eigenschaftsnamens als Zeichenfolge


90

Was ist der einfachste Weg, um gegen eine Eigenschaft in C # zu codieren, wenn ich den Eigenschaftsnamen als Zeichenfolge habe? Zum Beispiel möchte ich dem Benutzer erlauben, einige Suchergebnisse nach einer Eigenschaft seiner Wahl (unter Verwendung von LINQ) zu ordnen. Sie wählen die Eigenschaft "order by" in der Benutzeroberfläche - natürlich als Zeichenfolgenwert. Gibt es eine Möglichkeit, diese Zeichenfolge direkt als Eigenschaft der linq-Abfrage zu verwenden, ohne bedingte Logik (if / else, switch) verwenden zu müssen, um die Zeichenfolgen Eigenschaften zuzuordnen. Reflexion?

Logischerweise möchte ich Folgendes tun:

query = query.OrderBy(x => x."ProductId");

Update: Ich habe ursprünglich nicht angegeben, dass ich Linq to Entities verwende - es scheint, dass Reflection (zumindest der GetProperty-, GetValue-Ansatz) nicht in L2E übersetzt wird.


Ich denke, Sie müssten Reflektion verwenden, und ich bin nicht sicher, ob Sie Reflektion in einem Lambda-Ausdruck verwenden können ... Nun, mit ziemlicher Sicherheit nicht in Linq to SQL, aber vielleicht, wenn Sie Linq gegen eine Liste oder etwas anderes verwenden.
CodeRedick

@Telos: Es gibt keinen Grund, warum Sie Reflection (oder eine andere API) in einem Lambda nicht verwenden können. Ob es funktioniert, wenn der Code als Ausdruck ausgewertet und in etwas anderes übersetzt wird (wie LINQ-to-SQL, wie Sie vorschlagen), ist eine ganz andere Frage.
Adam Robinson

Aus diesem Grund habe ich einen Kommentar anstelle einer Antwort gepostet. ;) Meistens an Linq2SQL gewöhnt ...
CodeRedick

1
Musste nur das gleiche Problem überwinden .. siehe meine Antwort unten. stackoverflow.com/a/21936366/775114
Mark Powell

Antworten:


123

Ich würde diese Alternative zu dem anbieten, was alle anderen gepostet haben.

System.Reflection.PropertyInfo prop = typeof(YourType).GetProperty("PropertyName");

query = query.OrderBy(x => prop.GetValue(x, null));

Dies vermeidet wiederholte Aufrufe der Reflection-API zum Abrufen der Eigenschaft. Jetzt wird nur noch der Wert wiederholt.

jedoch

Ich würde die Verwendung von a PropertyDescriptorempfehlen, da dies TypeDescriptordie Zuweisung von benutzerdefinierten s zu Ihrem Typ ermöglicht, wodurch einfache Operationen zum Abrufen von Eigenschaften und Werten möglich sind. Wenn kein benutzerdefinierter Deskriptor vorhanden ist, wird er ohnehin auf die Reflexion zurückgreifen.

PropertyDescriptor prop = TypeDescriptor.GetProperties(typeof(YourType)).Find("PropertyName");

query = query.OrderBy(x => prop.GetValue(x));

HyperDescriptorSchauen Sie sich Marc Gravels Projekt auf CodeProject an, um es zu beschleunigen. Ich habe dies mit großem Erfolg verwendet; Es ist ein Lebensretter für leistungsstarke Datenbindung und dynamische Eigenschaftsoperationen für Geschäftsobjekte.


Beachten Sie, dass der reflektierte Aufruf (dh GetValue) der teuerste Teil der Reflexion ist. Das Abrufen von Metadaten (dh GetProperty) ist tatsächlich weniger kostspielig (um eine Größenordnung). Wenn Sie also diesen Teil zwischenspeichern, sparen Sie sich nicht wirklich so viel. Dies wird in beiden Fällen ziemlich gleich viel kosten, und diese Kosten werden hoch sein. Nur etwas zu beachten.
Jrista

1
@ jrista: Aufruf ist am teuersten, um sicher zu sein. "Weniger kostspielig" bedeutet jedoch nicht "kostenlos" oder sogar in der Nähe davon. Das Abrufen von Metadaten dauert nicht trivial, daher hat das Zwischenspeichern einen Vorteil und keinen Nachteil (es sei denn, ich vermisse hier etwas). In Wahrheit sollte dies PropertyDescriptorsowieso eine sein (um benutzerdefinierte Typdeskriptoren zu berücksichtigen, die das Abrufen von Werten zu einer einfachen Operation machen könnten ).
Adam Robinson

Stundenlang nach so etwas gesucht, um das programmgesteuerte Sortieren einer ASP.NET-GridView zu handhaben: PropertyDescriptor prop = TypeDescriptor.GetProperties (typeof (ScholarshipRequest)). Find (e.SortExpression, true);
Baxter

stackoverflow.com/questions/61635636/… Hatte ein Problem mit der Reflexion, das in EfCore 3.1.3 nicht funktioniert hat. Es scheint einen Fehler in EfCore 2 auszulösen, der für die Warnungen aktiviert werden muss. Verwenden Sie die Antwort von @Mark unten
Armourshield

Ich erhalte Folgendes: InvalidOperationException: Der LINQ-Ausdruck 'DbSet <MyObject> .Where (t => t.IsMasterData) .OrderBy (t => t.GetType (). GetProperty ("Address"). GetValue (obj: t, index: null) .GetType ()) 'konnte nicht übersetzt werden. Schreiben Sie die Abfrage entweder in einer Form um, die übersetzt werden kann, oder wechseln Sie explizit zur Clientbewertung, indem Sie einen Aufruf von AsEnumerable (), AsAsyncEnumerable (), ToList () oder ToListAsync () einfügen.
Bbrinck

62

Ich bin etwas spät zur Party, aber ich hoffe, das kann hilfreich sein.

Das Problem bei der Verwendung von Reflection besteht darin, dass der resultierende Ausdrucksbaum mit ziemlicher Sicherheit von keinem anderen Linq-Anbieter als dem internen .NET-Anbieter unterstützt wird. Dies ist für interne Sammlungen in Ordnung, funktioniert jedoch nicht, wenn die Sortierung vor der Paginierung an der Quelle (z. B. SQL, MongoDb usw.) erfolgen soll.

Das folgende Codebeispiel enthält IQueryable-Erweiterungsmethoden für OrderBy und OrderByDescending und kann wie folgt verwendet werden:

query = query.OrderBy("ProductId");

Verlängerungsmethode:

public static class IQueryableExtensions 
{
    public static IOrderedQueryable<T> OrderBy<T>(this IQueryable<T> source, string propertyName)
    {
        return source.OrderBy(ToLambda<T>(propertyName));
    }

    public static IOrderedQueryable<T> OrderByDescending<T>(this IQueryable<T> source, string propertyName)
    {
        return source.OrderByDescending(ToLambda<T>(propertyName));
    }

    private static Expression<Func<T, object>> ToLambda<T>(string propertyName)
    {
        var parameter = Expression.Parameter(typeof(T));
        var property = Expression.Property(parameter, propertyName);
        var propAsObject = Expression.Convert(property, typeof(object));

        return Expression.Lambda<Func<T, object>>(propAsObject, parameter);            
    }
}

Grüße, Mark.


Ausgezeichnete Lösung - genau das habe ich gesucht. Ich muss mich wirklich in die Ausdrucksbäume vertiefen. Immer noch sehr Anfänger. @ Mark, gibt es eine Lösung für verschachtelte Ausdrücke? Angenommen, ich habe einen Typ T mit einer Eigenschaft "Sub" vom Typ TSub, die selbst die Eigenschaft "Value" hat. Jetzt möchte ich den Ausdruck Ausdruck <Func <T, Objekt >> für die Zeichenfolge "Sub.Value" erhalten.
Simon Scheurer

4
Warum brauchen wir die Expression.Convertkonvertieren propertyzu object? Ich erhalte eine Unable to cast the type 'System.String' to type 'System.Object'. LINQ to Entities only supports casting EDM primitive or enumeration types.Fehlermeldung und das Entfernen scheint zu funktionieren.
ShuberFu

@ Demodave, wenn ich mich richtig erinnere. var propAsObject = Expression.Convert(property, typeof(object));und nur propertyanstelle vonpropAsObject
ShuberFu

Gold. Angepasst für einen .Net Core 2.0.5.
Chris Amelinckx

2
Erhaltener FehlerLINQ to Entities only supports casting EDM primitive or enumeration types
Mateusz Puwałowski

34

Ich mochte die Antwort von @Mark Powell , aber wie @ShuberFu sagte, gibt es den Fehler LINQ to Entities only supports casting EDM primitive or enumeration types.

Das Entfernen var propAsObject = Expression.Convert(property, typeof(object));funktionierte nicht mit Eigenschaften, bei denen es sich um Werttypen handelte, wie z. B. Ganzzahl, da das int-Objekt nicht implizit eingerahmt wurde.

Unter Verwendung von Ideen von Kristofer Andersson und Marc Gravell habe ich einen Weg gefunden, die abfragbare Funktion unter Verwendung des Eigenschaftsnamens zu erstellen und sie weiterhin mit Entity Framework funktionieren zu lassen. Ich habe auch einen optionalen IComparer-Parameter eingefügt. Achtung: Der IComparer-Parameter funktioniert nicht mit Entity Framework und sollte bei Verwendung von Linq to Sql weggelassen werden.

Folgendes funktioniert mit Entity Framework und Linq to Sql:

query = query.OrderBy("ProductId");

Und @Simon Scheurer das funktioniert auch:

query = query.OrderBy("ProductCategory.CategoryId");

Und wenn Sie Entity Framework oder Linq to Sql nicht verwenden, funktioniert dies:

query = query.OrderBy("ProductCategory", comparer);

Hier ist der Code:

public static class IQueryableExtensions 
{    
public static IOrderedQueryable<T> OrderBy<T>(this IQueryable<T> query, string propertyName, IComparer<object> comparer = null)
{
    return CallOrderedQueryable(query, "OrderBy", propertyName, comparer);
}

public static IOrderedQueryable<T> OrderByDescending<T>(this IQueryable<T> query, string propertyName, IComparer<object> comparer = null)
{
    return CallOrderedQueryable(query, "OrderByDescending", propertyName, comparer);
}

public static IOrderedQueryable<T> ThenBy<T>(this IOrderedQueryable<T> query, string propertyName, IComparer<object> comparer = null)
{
    return CallOrderedQueryable(query, "ThenBy", propertyName, comparer);
}

public static IOrderedQueryable<T> ThenByDescending<T>(this IOrderedQueryable<T> query, string propertyName, IComparer<object> comparer = null)
{
    return CallOrderedQueryable(query, "ThenByDescending", propertyName, comparer);
}

/// <summary>
/// Builds the Queryable functions using a TSource property name.
/// </summary>
public static IOrderedQueryable<T> CallOrderedQueryable<T>(this IQueryable<T> query, string methodName, string propertyName,
        IComparer<object> comparer = null)
{
    var param = Expression.Parameter(typeof(T), "x");

    var body = propertyName.Split('.').Aggregate<string, Expression>(param, Expression.PropertyOrField);

    return comparer != null
        ? (IOrderedQueryable<T>)query.Provider.CreateQuery(
            Expression.Call(
                typeof(Queryable),
                methodName,
                new[] { typeof(T), body.Type },
                query.Expression,
                Expression.Lambda(body, param),
                Expression.Constant(comparer)
            )
        )
        : (IOrderedQueryable<T>)query.Provider.CreateQuery(
            Expression.Call(
                typeof(Queryable),
                methodName,
                new[] { typeof(T), body.Type },
                query.Expression,
                Expression.Lambda(body, param)
            )
        );
}
}

Meine Güte, Mann, sind Sie Microsoft? :) Das AggregateFragment ist großartig! Es kümmert sich um virtuelle Ansichten, die aus dem EF Core-Modell mit erstellt wurden Join, da ich Eigenschaften wie "T.Property" verwende. Andernfalls Joinwäre eine Bestellung nach InvalidOperationExceptionoder entweder nicht möglich NullReferenceException. Und ich muss NACH bestellen Join, da die meisten Abfragen konstant sind, die Bestellungen in Ansichten nicht.
Harry

@ Harry. Danke, aber ich kann das AggregateFragment wirklich nicht genug würdigen. Ich glaube, es war eine Kombination aus Marc Gravells Code und einer Intellisense-Empfehlung. :)
David Specht

@DavidSpecht Ich lerne gerade Ausdrucksbäume, also ist alles an ihnen jetzt noch schwarze Magie für mich. Aber ich lerne schnell, das interaktive C # -Fenster in VS hilft sehr.
Harry

Wie benutzt man das?
Dat Nguyen

@Dat Nguyen Stattdessen products.OrderBy(x => x.ProductId)könnten Sie verwendenproducts.OrderBy("ProductId")
David Specht

12

Ja, ich glaube nicht, dass es einen anderen Weg als Reflection gibt.

Beispiel:

query = query.OrderBy(x => x.GetType().GetProperty("ProductId").GetValue(x, null));

5
query = query.OrderBy(x => x.GetType().GetProperty("ProductId").GetValue(x, null));

Ich habe versucht, die genaue Syntax auf den Kopf zu stellen, aber ich denke, das ist richtig.


2

Reflexion ist die Antwort!

typeof(YourType).GetProperty("ProductId").GetValue(theInstance);

Es gibt viele Dinge, die Sie tun können, um die reflektierten PropertyInfo zwischenzuspeichern, nach fehlerhaften Zeichenfolgen zu suchen, Ihre Abfragevergleichsfunktion zu schreiben usw., aber im Kern tun Sie dies.



2

Produktiver als Reflexionserweiterung auf dynamische Auftragspositionen:

public static class DynamicExtentions
{
    public static object GetPropertyDynamic<Tobj>(this Tobj self, string propertyName) where Tobj : class
    {
        var param = Expression.Parameter(typeof(Tobj), "value");
        var getter = Expression.Property(param, propertyName);
        var boxer = Expression.TypeAs(getter, typeof(object));
        var getPropValue = Expression.Lambda<Func<Tobj, object>>(boxer, param).Compile();            
        return getPropValue(self);
    }
}

Beispiel:

var ordered = items.OrderBy(x => x.GetPropertyDynamic("ProductId"));

Möglicherweise müssen Sie auch konforme Lambas zwischenspeichern (z. B. im Wörterbuch <>).


1

Auch dynamische Ausdrücke können dieses Problem lösen. Sie können stringbasierte Abfragen über LINQ-Ausdrücke verwenden, die zur Laufzeit möglicherweise dynamisch erstellt wurden.

var query = query
          .Where("Category.CategoryName == @0 and Orders.Count >= @1", "Book", 10)
          .OrderBy("ProductId")
          .Select("new(ProductName as Name, Price)");

0

Ich denke, wir können einen leistungsstarken Werkzeugnamen Ausdruck verwenden und in diesem Fall wie folgt als Erweiterungsmethode verwenden:

public static IOrderedQueryable<T> OrderBy<T>(this IQueryable<T> source, string ordering, bool descending)
{
    var type = typeof(T);
    var property = type.GetProperty(ordering);
    var parameter = Expression.Parameter(type, "p");
    var propertyAccess = Expression.MakeMemberAccess(parameter, property);
    var orderByExp = Expression.Lambda(propertyAccess, parameter);
    MethodCallExpression resultExp = 
        Expression.Call(typeof(Queryable), (descending ? "OrderByDescending" : "OrderBy"), 
            new Type[] { type, property.PropertyType }, source.Expression, Expression.Quote(orderByExp));
    return (IOrderedQueryable<T>)source.Provider.CreateQuery<T>(resultExp);
}
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.