Какой самый простой способ кодировать свойство на C #, когда у меня есть имя свойства в виде строки? Например, я хочу разрешить пользователю упорядочивать некоторые результаты поиска по свойству по своему выбору (с использованием LINQ). Они выберут свойство «порядок по» в пользовательском интерфейсе - конечно, как строковое значение. Есть ли способ использовать эту строку непосредственно как свойство запроса linq, без использования условной логики (if / else, switch) для сопоставления строк со свойствами. Отражение?
По логике вещей я бы хотел сделать следующее:
query = query.OrderBy(x => x."ProductId");
Обновление: я изначально не указывал, что использую Linq to Entities - похоже, что отражение (по крайней мере, подход GetProperty, GetValue) не переводится в L2E.
c#
.net
linq
linq-to-entities
Джереми
источник
источник
Ответы:
Я бы предложил эту альтернативу тому, что опубликовали все остальные.
System.Reflection.PropertyInfo prop = typeof(YourType).GetProperty("PropertyName"); query = query.OrderBy(x => prop.GetValue(x, null));
Это позволяет избежать повторных вызовов API отражения для получения свойства. Теперь единственный повторный вызов - получение значения.
Однако
Я бы рекомендовал использовать
PropertyDescriptor
вместо этого, так как это позволитTypeDescriptor
назначать настраиваемые s вашему типу, что позволит использовать облегченные операции для получения свойств и значений. При отсутствии настраиваемого дескриптора он все равно вернется к отражению.PropertyDescriptor prop = TypeDescriptor.GetProperties(typeof(YourType)).Find("PropertyName"); query = query.OrderBy(x => prop.GetValue(x));
Что касается ускорения, посмотрите
HyperDescriptor
проект Marc Gravel на CodeProject. Я использовал это с большим успехом; это спасатель для высокопроизводительной привязки данных и операций с динамическими свойствами бизнес-объектов.источник
PropertyDescriptor
любом случае (для учета дескрипторов пользовательского типа, которые могут сделать получение значения легкой операцией).Я немного опоздал на вечеринку, но надеюсь, что это может мне помочь.
Проблема с использованием отражения заключается в том, что результирующее дерево выражений почти наверняка не будет поддерживаться какими-либо поставщиками Linq, кроме внутреннего поставщика .Net. Это нормально для внутренних коллекций, однако это не сработает, если сортировка должна выполняться в источнике (будь то SQL, MongoDb и т. Д.) Перед разбивкой на страницы.
В приведенном ниже примере кода представлены методы расширения IQueryable для 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); } }
С уважением, Марк.
источник
Expression.Convert
конвертироватьproperty
вobject
? Я получаю сообщениеUnable to cast the type 'System.String' to type 'System.Object'. LINQ to Entities only supports casting EDM primitive or enumeration types.
об ошибке, и его удаление, похоже, работает.var propAsObject = Expression.Convert(property, typeof(object));
и просто используйтеproperty
вместоpropAsObject
LINQ to Entities only supports casting EDM primitive or enumeration types
Мне понравился ответ от @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) ) ); } }
источник
Aggregate
фрагмент потрясающий! Он заботится о виртуальных представлениях, созданных из модели EF CoreJoin
, поскольку я использую такие свойства, как "T.Property". В противном случае сделать заказ послеJoin
было бы невозможно произвести ниInvalidOperationException
илиNullReferenceException
. И мне нужно заказывать ПОСЛЕJoin
, потому что большинство запросов постоянны, а заказы в представлениях - нет.Aggregate
фрагмент. Я считаю, что это была комбинация кода Марка Грейвелла и рекомендации intellisense. :)products.OrderBy(x => x.ProductId)
вы могли бы использоватьproducts.OrderBy("ProductId")
Да, я не думаю, что есть другой способ, кроме Reflection.
Пример:
query = query.OrderBy(x => x.GetType().GetProperty("ProductId").GetValue(x, null));
источник
"LINQ to Entities does not recognize the method 'System.Object GetValue(System.Object)' method, and this method cannot be translated into a store expression."
Любые мысли или советы, пожалуйста?query = query.OrderBy(x => x.GetType().GetProperty("ProductId").GetValue(x, null));
Пытаюсь вспомнить точный синтаксис из головы, но я думаю, что это правильно.
источник
Отражение - это ответ!
typeof(YourType).GetProperty("ProductId").GetValue(theInstance);
Есть много вещей, которые вы можете сделать для кэширования отраженной PropertyInfo, проверки на наличие плохих строк, написания функции сравнения запросов и т. Д., Но по сути это то, что вы делаете.
источник
Вы можете использовать динамический Linq - проверьте это блогом.
Также посмотрите этот пост на StackOverFlow ...
источник
Более продуктивно, чем расширение отражения для динамических элементов заказа:
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"));
Также вам может потребоваться кешировать собранные лямб-выражения (например, в Dictionary <>)
источник
Также динамические выражения могут решить эту проблему. Вы можете использовать строковые запросы через выражения LINQ, которые можно было бы создать динамически во время выполнения.
var query = query .Where("Category.CategoryName == @0 and Orders.Count >= @1", "Book", 10) .OrderBy("ProductId") .Select("new(ProductName as Name, Price)");
источник
Я думаю, мы можем использовать мощный инструмент под названием Expression, а в этом случае использовать его как метод расширения следующим образом:
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); }
источник