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,因为这将允许将自定义TypeDescriptors 分配给您的类型,从而可以使用轻量级操作来检索属性和值。在没有自定义描述符的情况下,无论如何它都会退回到反射。

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 .Where(t => t.IsMasterData) .OrderBy(t => t.GetType().GetProperty("Address" ).GetValue(obj: t, index: null).GetType())' 无法翻译。以可翻译的形式重写查询,或通过插入对 AsEnumerable()、AsAsyncEnumerable()、ToList() 或 ToListAsync() 的调用显式切换到客户端评估。【参考方案2】:

我参加聚会有点晚了,不过,我希望这能有所帮助。

使用反射的问题是生成的表达式树几乎肯定不会被除内部 .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”获取表达式 Expression>。 为什么我们需要Expression.Convertproperty 转换为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 之后订购将不可能产生InvalidOperationExceptionNullReferenceException。而且我确实需要在Join 之后订购,因为大多数查询是不变的,视图中的订单不是。 @DavidSpecht 我只是在学习表达式树,所以关于它们的一切现在对我来说都是黑魔法。但是我学得很快,VS中的C#交互窗口帮助很大。 这个怎么用? @Dat Nguyen 你可以使用products.OrderBy(x =&gt; 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&lt;TElement&gt; 来处理内存

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# - 使用属性名称作为字符串按属性排序的代码[重复]的主要内容,如果未能解决你的问题,请参考以下文章

C# - 使用属性名称作为字符串由属性排序的代码

在 C# 中按名称和日期作为键对字典进行排序? [复制]

按字符串属性C#对对象列表进行排序[重复]

按属性名称(字符串值)排序列表? [复制]

C#如何将变量用作键值字符串数组的对象列表对象构造函数中的属性名称

根据字符串属性按字母顺序对对象数组进行排序