С# - код для заказа по свойству, используя имя свойства в виде строки

Какой самый простой способ закодировать свойство в С#, когда у меня есть имя свойства в виде строки? Например, я хочу разрешить пользователю заказывать некоторые результаты поиска по своему выбору (используя LINQ). Они сами выбирают свойство "порядок по" в пользовательском интерфейсе - как строковое значение. Есть ли способ использовать эту строку непосредственно как свойство запроса linq, без необходимости использовать условную логику (if/else, switch) для сопоставления строк свойствам. Отражение?

Логически, это то, что я хотел бы сделать:

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

Обновление: Я не изначально указывал, что я использую Linq для Entities - кажется, что отражение (по крайней мере, метод GetProperty, GetValue) не переводится в L2E.

Ответ 1

Я бы предложил эту альтернативу тому, что разместили все остальные.

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

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

Это позволяет избежать повторных вызовов API отражения для получения свойства. Теперь единственным повторным вызовом является получение значения.

Однако

Вместо этого я бы рекомендовал использовать PropertyDescriptor, так как это позволит назначить пользовательский TypeDescriptor для вашего типа, что позволяет иметь легкие операции для извлечения свойств и значений. В отсутствие пользовательского дескриптора он все равно будет возвращаться к отражению.

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

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

Чтобы ускорить его, просмотрите проект Marc Gravel HyperDescriptor в CodeProject. Я использовал это с большим успехом; это спасатель жизни для высокопроизводительных привязок данных и динамических операций свойств бизнес-объектов.

Ответ 2

Я немного опаздываю на вечеринку, но надеюсь, что это может помочь.

Проблема с использованием рефлексии заключается в том, что полученное Дерево выражений почти наверняка не будет поддерживаться никакими провайдерами Linq, отличными от внутреннего поставщика .Net. Это нормально для внутренних коллекций, однако это не будет работать, когда сортировка должна выполняться в источнике (будь то SQL, MongoDb и т.д.) До разбивки на страницы.

В приведенном ниже примере кода представлены методы IQueryable extention для OrderBy и OrderByDescending и могут быть использованы следующим образом:

query = query.OrderBy("ProductId");

Метод расширения:

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

С уважением, Марк.

Ответ 3

Мне понравился ответ @Mark Powell, но, как сказал @ShuberFu, он выдает ошибку, LINQ to Entities only supports casting EDM primitive or enumeration types.

Удаление var propAsObject = Expression.Convert(property, typeof(object)); не работал со свойствами, являющимися типами значений, такими как целочисленные, поскольку он неявно не помещал бы int в объект.

Используя идеи Кристофера Андерссона и Марка Гравелла, я нашел способ сконструировать функцию Queryable, используя имя свойства и по-прежнему работать с Entity Framework. Я также включил необязательный параметр IComparer. Внимание: параметр IComparer не работает с Entity Framework и должен быть пропущен при использовании Linq to Sql.

Следующее работает с Entity Framework и Linq to Sql:

query = query.OrderBy("ProductId");

И @Simon Scheurer это тоже работает:

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

И если вы не используете Entity Framework или Linq to Sql, это работает:

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

Вот код:

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

Ответ 4

Да, я не думаю, что есть другой способ, чем Reflection.

Пример:

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

Ответ 5

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

Попытка вспомнить точный синтаксис с моей головы, но я думаю, что это правильно.

Ответ 6

Отражение - это ответ!

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

Есть много вещей, которые вы можете сделать для кэширования отраженного PropertyInfo, проверки на наличие плохих строк, написания функции сравнения запросов и т.д., но в основе всего этого вы делаете.

Ответ 7

Вы можете использовать динамический Linq - просмотрите этот блог.

Также проверьте этот пост StackOverFlow...

Ответ 8

Более продуктивно, чем расширение отражения для элементов динамического заказа:

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

Пример:

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

Также вам может понадобиться кэшировать lambas (например, в словаре <>)

Ответ 9

Также динамические выражения могут решить эту проблему. Вы можете использовать строковые запросы через выражения LINQ, которые могли бы быть динамически созданы во время выполнения.

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

Ответ 10

Добавление нового, улучшенного решения на текущий год. Отражение должно быть вашим последним выбором, поскольку у нас есть деревья выражений, которые можно использовать для генерации пользовательского кода/делегатов во время выполнения. Также обратите внимание, что, согласно другому ответу, получение PropertyInfo и его повторное использование не .GetValue() производительность, поскольку именно метод .GetValue() несет стоимость.

public class Product
{
    public int ProductId { get; set; }
}

static Func<T, object> GetPropertyAccessor<T>(string propertyName)
{
    var typeOfT = typeof(T);
    var pi = typeOfT.GetProperty(propertyName);

    var p = Expression.Parameter(typeOfT);
    var memberAccess = Expression.Convert(Expression.MakeMemberAccess(p, pi), typeof(object));

    //Put a breakpoint here and look at the DebugView property where you can see that
    //it generated a call to the ProductId property
    var lami = Expression.Lambda<Func<T, object>>(memberAccess, p);

    //This line compiles the above expression into a reusable delegate
    var deli = lami.Compile();

    //Return it for use
    return deli;
}

Используйте это так:

//Get your custom delegate
var deli = GetPropertyAccessor<Product>("ProductId");

//Use it directly in LINQ to entities (or objects)
var ordered = products.OrderByDescending(deli).ToArray();

Код, сгенерированный деревом выражений в GetPropertyAccessor находится здесь:

.Lambda #Lambda1<System.Func'2[Program+Product,System.Object]>(Program+Product $var1) {
    (System.Object)$var1.ProductId
}

Это в основном эквивалент того, как вы создали конкретный метод для получения ProductId:

static int GetProductId(Product product)
{
    return product.ProductId;
}

Затем вы передадите ссылку на этот метод в свой запрос, как вы сделали с сгенерированным делегатом:

var ordered = products.OrderByDescending(GetProductId).ToArray();

Вы можете сделать намного больше с деревьями выражений. Я рекомендую всем изучать их.