C# - kod do zamówienia przez właściwość używając nazwy właściwości jako łańcucha znaków

Jaki jest najprostszy sposób kodowania właściwości w C#, gdy mam nazwę właściwości jako łańcuch znaków? Na przykład chcę, aby użytkownik mógł zamówić niektóre wyniki wyszukiwania według wybranej przez siebie właściwości (używając LINQ). Wybierają właściwość "order by" w interfejsie użytkownika - oczywiście jako wartość łańcuchową. Czy istnieje sposób, aby użyć tego łańcucha bezpośrednio jako właściwości zapytania linq, bez konieczności używania logiki warunkowej (if/else, switch) do mapowania łańcuchów do właściwości. Odbicie?

Logicznie, to jest to, co chciałbym zrobić:

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

Update: Nie podałem oryginalnie, że używam Linq do encji - wygląda na to, że reflection (przynajmniej podejście GetProperty, GetValue) nie przekłada się na L2E.

Author: devi, 2009-11-06

9 answers

Chciałbym zaoferować tę alternatywę dla tego, co wszyscy inni opublikowali.

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

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

Pozwala to uniknąć wielokrotnych wywołań do API reflection w celu uzyskania właściwości. Teraz jedynym powtarzanym wywołaniem jest uzyskanie wartości.

Jednakże

Zalecałbym użycie PropertyDescriptor zamiast tego, ponieważ pozwoli to na przypisanie niestandardowych TypeDescriptor s do twojego typu, co umożliwi łatwe operacje pobierania właściwości i wartości. W przypadku braku deskryptora niestandardowego to i tak wróci do refleksji.

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

query = query.OrderBy(x => prop.GetValue(x));
Jeśli chodzi o przyspieszenie, sprawdź Marc Gravel ' s HyperDescriptor projekt na CodeProject. Korzystałem z tego z wielkim sukcesem; jest to narzędzie oszczędzające życie dla wysokowydajnego wiązania danych i dynamicznych operacji na obiektach biznesowych.
 87
Author: Adam Robinson,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-11-06 18:50:50

Jestem trochę spóźniona na imprezę, jednak mam nadzieję, że to może pomóc.

Problem z użyciem reflection polega na tym, że wynikowe drzewo wyrażeń prawie na pewno nie będzie obsługiwane przez żadnych dostawców Linq innych niż wewnętrzny dostawca.Net. Jest to dobre dla zbiorów wewnętrznych, jednak nie będzie to działać tam, gdzie sortowanie ma być wykonane u źródła (np. SQL, MongoDb, itp.) przed paginacją.

Poniższy przykład kodu zawiera IQueryable extension methods for OrderBy i OrderByDescending, i mogą być używane tak:

query = query.OrderBy("ProductId");

Metoda Rozszerzenia:

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);            
    }
}
Pozdrawiam, Mark.
 44
Author: Mark Powell,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-06-26 15:06:11

Podobała mi się odpowiedź od @ Mark Powell , ale jak powiedział @ShuberFu, podaje błąd LINQ to Entities only supports casting EDM primitive or enumeration types.

Usunięcie var propAsObject = Expression.Convert(property, typeof(object)); nie zadziałało z właściwościami, które były typami wartości, takimi jak integer, ponieważ nie byłoby domyślnie pola int do obiektu.

Używając pomysłów z Kristofer Andersson i Marc Gravell znalazłem sposób, aby skonstruować funkcję kwerendy używając nazwy właściwości i aby nadal działała z encjami. Dodałem również opcjonalny Parametr IComparer. Uwaga: parametr IComparer nie działa z frameworkiem encji i należy go pominąć, jeśli używa się Linq do Sql.

[[6]} poniższe działa z Entity Framework i Linq do Sql:
query = query.OrderBy("ProductId");

I @ Simon Scheurer to również działa:

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

I jeśli nie używasz encji Framework lub Linq do Sql, działa to:

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

Oto kod:

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)
            )
        );
}
}
 16
Author: David Specht,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-09-28 17:46:59

Tak, myślę, że nie ma innego sposobu niż odbicie.

Przykład:

query = query.OrderBy(x => x.GetType().GetProperty("ProductId").GetValue(x, null));
 11
Author: Alon Gubkin,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-11-06 17:47:53
query = query.OrderBy(x => x.GetType().GetProperty("ProductId").GetValue(x, null));
Próbuję sobie przypomnieć dokładną składnię, ale myślę, że to prawda.
 5
Author: dkackman,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-11-06 17:51:01

Refleksja jest odpowiedzią!

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

Jest wiele rzeczy, które możesz zrobić, aby buforować odbite Właściwośćinfo, sprawdzić, czy nie ma złych łańcuchów, napisać funkcję porównywania zapytań itp. ale w jego sercu, to jest to, co robisz.

 2
Author: Sebastian Good,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-11-06 17:49:42

Możesz użyć dynamicznego Linq-sprawdź Ten blog.

Sprawdź również ten post StackOverFlow...

 2
Author: Partha Choudhury,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:03:08

Również wyrażenia dynamiczne mogą rozwiązać ten problem. Możesz używać zapytań opartych na łańcuchach za pomocą wyrażeń LINQ, które mogły być dynamicznie konstruowane w czasie wykonywania.

var query = query
          .Where("Category.CategoryName == @0 and Orders.Count >= @1", "Book", 10)
          .OrderBy("ProductId")
          .Select("new(ProductName as Name, Price)");
 1
Author: ali-myousefi,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-02-06 08:36:24

Bardziej produktywne niż rozszerzenie refleksyjne do dynamicznych elementów porządku:

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

Przykład:

var ordered = items.OrderBy(x => x.GetPropertyDynamic("ProductId"));
 1
Author: devi,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-03-01 14:28:19