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

时间:2009-11-06 17:45:58

标签: c# .net linq linq-to-entities

当我将属性名称作为字符串时,对C#中的属性进行编码的最简单方法是什么?例如,我想允许用户通过他们选择的属性(使用LINQ)来订购一些搜索结果。他们将在UI中选择“order by”属性 - 当然是字符串值。有没有办法直接使用该字符串作为linq查询的属性,而不必使用条件逻辑(if / else,switch)将字符串映射到属性。反射?

从逻辑上讲,这就是我想要做的事情:

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

更新: 我最初并未指定我使用Linq to Entities - 看起来反射(至少GetProperty,GetValue方法)不会转换为L2E。

10 个答案:

答案 0 :(得分:96)

我会提供其他人发布的替代方案。

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在CodeProject上的HyperDescriptor项目。我用它取得了巨大的成功;它可以为业务对象的高性能数据绑定和动态属性操作提供生命保护。

答案 1 :(得分:49)

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

使用反射的问题在于,除了内部.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);            
    }
}

问候,马克。

答案 2 :(得分:19)

我喜欢@Mark Powell的答案,但正如@ShuberFu所述,它会给出错误LINQ to Entities only supports casting EDM primitive or enumeration types

删除var propAsObject = Expression.Convert(property, typeof(object));并不能处理值类型的属性,例如整数,因为它不会隐式地将int封装到对象中。

使用来自Kristofer AnderssonMarc Gravell的点子我找到了一种使用属性名称构建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)
            )
        );
}
}

答案 3 :(得分:12)

是的,我认为还有另一种方式而不是反思。

示例:

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

答案 4 :(得分:5)

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

试着回想起我的头脑中的确切语法,但我认为这是正确的。

答案 5 :(得分:2)

反思就是答案!

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

你可以做很多事情来缓存反映的PropertyInfo,检查错误的字符串,编写查询比较函数等等,但从本质上讲,这就是你所做的。

答案 6 :(得分:2)

您可以使用动态Linq - 查看this博客。

同时查看this StackOverFlow帖子......

答案 7 :(得分:2)

比动态订单商品的反映扩展效率更高:

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(例如,在Dictionary&lt;&gt;中)

答案 8 :(得分:1)

同样Dynamic Expressions可以解决这个问题。 您可以通过可能在运行时动态构建的LINQ表达式使用基于字符串的查询。

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

答案 9 :(得分:1)

警告⚠

如果数据在内存中,您可以使用 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

enter image description here

解决方案✅

通过使用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();

请参阅下图以更深入地了解。

enter image description here

Demo on dotnetfiddle