C# - 使用属性名称作为字符串按属性排序的代码[重复]
Posted
技术标签:
【中文标题】C# - 使用属性名称作为字符串按属性排序的代码[重复]【英文标题】:C# - code to order by a property using the property name as a string [duplicate] 【发布时间】:2009-11-06 17:45:58 【问题描述】:当我将属性名称作为字符串时,在 C# 中对属性进行编码的最简单方法是什么?例如,我想允许用户通过他们选择的属性(使用 LINQ)对一些搜索结果进行排序。他们将在 UI 中选择“order by”属性——当然是作为字符串值。有没有办法将该字符串直接用作 linq 查询的属性,而不必使用条件逻辑(if/else,switch)将字符串映射到属性。反射?
从逻辑上讲,这是我想做的:
query = query.OrderBy(x => x."ProductId");
更新:
我最初并没有指定我使用的是Linq to Entities
- 似乎反射(至少是 GetProperty、GetValue 方法)不会转换为 L2E。
【问题讨论】:
我认为你必须使用反射,我不确定你是否可以在 lambda 表达式中使用反射......好吧,几乎可以肯定不是在 Linq to SQL 中,但可能在使用 Linq 反对一个清单什么的。 @Telos:没有理由不能在 lambda 中使用反射(或任何其他 API)。如果代码被评估为表达式并翻译成其他东西(如您建议的 LINQ-to-SQL),它是否会工作完全是另一个问题。 这就是我发表评论而不是答案的原因。 ;) 主要用于 Linq2SQL... 只需要克服同样的问题..请参阅下面的答案。 ***.com/a/21936366/775114 【参考方案1】:我会提供这个替代其他人发布的内容。
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));
至于加快速度,请查看 Marc Gravel 在 CodeProject 上的 HyperDescriptor
项目。我已经成功地使用了它;它是对业务对象进行高性能数据绑定和动态属性操作的救星。
【讨论】:
请注意,反射调用(即 GetValue)是反射中成本最高的部分。元数据检索(即 GetProperty)实际上成本更低(一个数量级),因此通过缓存该部分,您并没有真正为自己节省那么多。这两种方式的成本几乎相同,而且成本会很高。只是需要注意的一点。 @jrista:可以肯定,调用是最昂贵的。然而,“成本更低”并不意味着“免费”,甚至接近于它。元数据检索需要大量时间,因此缓存它是有好处的,而且没有缺点(除非我在这里遗漏了一些东西)。事实上,无论如何这都应该使用PropertyDescriptor
(以考虑自定义类型描述符,这可以使值检索成为轻量级操作)。
搜索了几个小时来以编程方式处理对 ASP.NET GridView 进行排序:PropertyDescriptor prop = TypeDescriptor.GetProperties(typeof(ScholarshipRequest)).Find(e.SortExpression, true);
***.com/questions/61635636/… 在 EfCore 3.1.3 中无法解决反射问题。它似乎在 EfCore 2 中引发错误,需要为警告激活。使用下面@Mark 的答案
我收到以下信息: InvalidOperationException: The LINQ expression 'DbSet我参加聚会有点晚了,不过,我希望这能有所帮助。
使用反射的问题是生成的表达式树几乎肯定不会被除内部 .Net 提供程序之外的任何 Linq 提供程序支持。这对于内部集合来说很好,但是在分页之前在源(例如 SQL、MongoDb 等)进行排序的情况下,这将不起作用。
下面的代码示例为 OrderBy 和 OrderByDescending 提供了 IQueryable 扩展方法,可以这样使用:
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);
问候,马克。
【讨论】:
优秀的解决方案 - 我正在寻找那个。我真的需要深入研究表达式树。在那方面还是很菜鸟。 @Mark,做嵌套表达式的任何解决方案?假设我有一个 T 类型,其 TSub 类型的属性“Sub”本身具有属性“Value”。现在我想为字符串“Sub.Value”获取表达式 ExpressionExpression.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.
错误,删除它似乎有效。
@Demodave 如果我没记错的话。 var propAsObject = Expression.Convert(property, typeof(object));
并使用 property
代替 propAsObject
黄金。适用于 .Net Core 2.0.5。
得到错误LINQ to Entities only supports casting EDM primitive or enumeration types
【参考方案3】:
我喜欢@Mark Powell 的答案,但正如@ShuberFu 所说,它给出了错误LINQ to Entities only supports casting EDM primitive or enumeration types
。
删除 var propAsObject = Expression.Convert(property, typeof(object));
不适用于值类型的属性,例如整数,因为它不会隐式地将 int 装箱到对象。
使用来自 Kristofer Andersson 和 Marc Gravell 的想法我找到了一种使用属性名称构造 Queryable 函数的方法,并且它仍然适用于实体框架。我还包括了一个可选的 IComparer 参数。 注意: IComparer 参数不适用于实体框架,如果使用 Linq to Sql,则应省略。
以下适用于 Entity Framework 和 Linq to Sql:
query = query.OrderBy("ProductId");
@Simon Scheurer 这也有效:
query = query.OrderBy("ProductCategory.CategoryId");
如果您不使用实体框架或 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
片段太棒了!它负责使用 Join
从 EF Core 模型创建的虚拟视图,因为我使用诸如“T.Property”之类的属性。否则在Join
之后订购将不可能产生InvalidOperationException
或NullReferenceException
。而且我确实需要在Join
之后订购,因为大多数查询是不变的,视图中的订单不是。
@DavidSpecht 我只是在学习表达式树,所以关于它们的一切现在对我来说都是黑魔法。但是我学得很快,VS中的C#交互窗口帮助很大。
这个怎么用?
@Dat Nguyen 你可以使用products.OrderBy(x => x.ProductId)
,而不是products.OrderBy("ProductId")
哇,这对我有用,谢谢【参考方案4】:
是的,我认为除了反射之外没有其他方法。
例子:
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."
有什么想法或建议吗?
您可以查看我的answer below 了解@FlorinVîrdol 的原因【参考方案5】:
query = query.OrderBy(x => x.GetType().GetProperty("ProductId").GetValue(x, null));
试图回忆起我脑海中的确切语法,但我认为这是正确的。
【讨论】:
【参考方案6】:警告⚠
如果数据在内存中,您可以使用Reflection
。否则,当您使用 Linq-2-EF, Linq-2-SQL, etc.
时,您将看到如下所示的错误
@Florin Vîrdol's comment
LINQ to Entities 无法识别方法“System.Object” GetValue(System.Object)' 方法,该方法无法翻译 到商店表达式中。
为什么⁉
因为当您编写代码以向Linq query provider
提供查询时。它首先被翻译成SQL语句,然后在数据库服务器上执行。
(见下图,来自https://www.tutorialsteacher.com/linq/linq-expression)
解决方案✅
通过使用Expression tree,您可以编写这样的泛型方法
public static IEnumerable<T> OrderDynamic<T>(IEnumerable<T> Data, string propToOrder)
var param = Expression.Parameter(typeof(T));
var memberAccess = Expression.Property(param, propToOrder);
var convertedMemberAccess = Expression.Convert(memberAccess, typeof(object));
var orderPredicate = Expression.Lambda<Func<T, object>>(convertedMemberAccess, param);
return Data.AsQueryable().OrderBy(orderPredicate).ToArray();
并像这样使用它
var result = OrderDynamic<Student>(yourQuery, "StudentName"); // string property
或
var result = OrderDynamic<Student>(yourQuery, "Age"); // int property
它还可以通过在你的通用方法返回语句中将你的数据转换为IQueryable<TElement>
来处理内存
return Data.AsQueryable().OrderBy(orderPredicate).ToArray();
请参阅下图以了解更多信息。
Demo on dotnetfiddle
【讨论】:
【参考方案7】:反思就是答案!
typeof(YourType).GetProperty("ProductId").GetValue(theInstance);
您可以做很多事情来缓存反映的 PropertyInfo、检查错误字符串、编写查询比较函数等,但本质上,这就是您要做的事情。
【讨论】:
【参考方案8】:您可以使用动态 Linq - 查看 this 博客。
还可以查看this *** 帖子...
【讨论】:
这对我来说是最好的答案【参考方案9】:比动态订单项的反射扩展更有效率:
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(例如在字典中)
【讨论】:
【参考方案10】:Dynamic Expressions也可以解决这个问题。 您可以通过可能在运行时动态构建的 LINQ 表达式使用基于字符串的查询。
var query = query
.Where("Category.CategoryName == @0 and Orders.Count >= @1", "Book", 10)
.OrderBy("ProductId")
.Select("new(ProductName as Name, Price)");
【讨论】:
【参考方案11】:我认为我们可以使用一个强大的工具名称表达式,在这种情况下将其用作扩展方法,如下所示:
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);
【讨论】:
以上是关于C# - 使用属性名称作为字符串按属性排序的代码[重复]的主要内容,如果未能解决你的问题,请参考以下文章