wake-up-neo.com

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

Was ist die einfachste Methode zum Codieren einer Eigenschaft in C #, wenn ich den Eigenschaftsnamen als Zeichenfolge habe? Zum Beispiel möchte ich dem Benutzer erlauben, einige Suchergebnisse nach einer Eigenschaft seiner Wahl zu ordnen (unter Verwendung von LINQ). Sie wählen die Eigenschaft "order by" in der Benutzeroberfläche aus - 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) zu verwenden, um die Zeichenfolgen Eigenschaften zuzuordnen. Betrachtung?

Das würde ich logischerweise gerne 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 GetProperty, GetValue) nicht in L2E übersetzt wird.

69
Jeremy

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

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

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

Dadurch werden wiederholte Aufrufe der Reflection-API zum Abrufen der Eigenschaft vermieden. Jetzt erhält der einzige wiederholte Aufruf den Wert.

Jedoch

Ich würde empfehlen, stattdessen PropertyDescriptor zu verwenden, da dies die Zuweisung von benutzerdefinierten TypeDescriptors zu Ihrem Typ ermöglicht, wodurch einfache Operationen zum Abrufen von Eigenschaften und Werten möglich werden. In Abwesenheit eines benutzerdefinierten Deskriptors wird es sowieso auf Reflexion zurückgreifen.

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

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

Informationen zur Beschleunigung finden Sie im CodeProject-Projekt HyperDescriptor von Marc Gravel. Ich habe das mit großem Erfolg benutzt; Es ist ein Lebensretter für leistungsstarke Datenbindung und dynamische Eigenschaftsoperationen an Geschäftsobjekten.

100
Adam Robinson

Ich bin ein bisschen zu 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 in Ordnung für interne Sammlungen, funktioniert jedoch nicht, wenn die Sortierung an der Quelle (z. B. SQL, MongoDb usw.) vor der Paginierung erfolgen soll.

Das folgende Codebeispiel enthält IQueryable-Erweiterungsmethoden für OrderBy und OrderByDescending und kann folgendermaßen verwendet werden:

query = query.OrderBy("ProductId");

Erweiterungsmethode:

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.

51
Mark Powell

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

Das Entfernen von var propAsObject = Expression.Convert(property, typeof(object)); funktionierte nicht mit Eigenschaften, bei denen es sich um Werttypen handelte, z. B. Ganzzahlen, da das Objekt int to nicht implizit umrahmt würde.

Unter Verwendung der Ideen von Kristofer Andersson und Marc Gravell habe ich eine Möglichkeit gefunden, die Funktion Queryable unter Verwendung des Eigenschaftsnamens zu konstruieren, und sie funktioniert weiterhin mit Entity Framework. Ich habe auch einen optionalen IComparer-Parameter eingefügt. Achtung: Der Parameter IComparer funktioniert nicht mit Entity Framework und sollte weggelassen werden, wenn Linq to Sql verwendet wird.

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 Folgendes:

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)
            )
        );
}
}
23
David Specht

Ja, ich glaube, es gibt keinen anderen Weg als Reflection.

Beispiel:

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

Ich habe versucht, die exakte Syntax aus dem Kopf zu verlieren, aber ich denke, das ist richtig.

5
dkackman

Sie können dynamisches Linq verwenden - check out this blog.

Schauen Sie sich auch this StackOverFlow-Post an ...

2

Reflexion ist die Antwort!

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

Es gibt eine Menge Dinge, die Sie tun können, um die reflektierten PropertyInfo zwischenzuspeichern, auf fehlerhafte Zeichenfolgen zu prüfen, Ihre Abfragevergleichsfunktion zu schreiben usw. Aber im Grunde ist dies das, was Sie tun.

2
Sebastian Good

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 kompatible Lambas zwischenspeichern (z. B. im Wörterbuch <>).

2
devi

Auch Dynamic Expressions kann dieses Problem lösen. Sie können zeichenfolgenbasierte Abfragen über LINQ-Ausdrücke verwenden, die zur Laufzeit dynamisch erstellt wurden.

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