首页 文章

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

提问于
浏览
64

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

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

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

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

9 回答

  • 2

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

    System.Reflection.PropertyInfo prop = typeof(YourType).GetProperty("PropertyName");
    
    query = query.OrderBy(x => prop.GetValue(x, null));
    

    这避免了重复调用反射API以获取属性 . 现在唯一重复的调用是获取值 .

    However

    我主张使用 PropertyDescriptor ,因为这将允许将自定义 TypeDescriptor 分配给您的类型,从而可以使用轻量级操作来检索属性和值 . 在没有自定义描述符的情况下,它无论如何都会回归到反射 .

    PropertyDescriptor prop = TypeDescriptor.GetProperties(typeof(YourType)).Find("PropertyName");
    
    query = query.OrderBy(x => prop.GetValue(x));
    

    至于加速,请查看Marc Gravel在CodeProject上的HyperDescriptor项目 . 对于业务对象的高性能数据绑定和动态属性操作,我可以节省大量时间 .

  • 12

    我有点迟到了,但是,我希望这会有所帮助 .

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

    问候,马克 .

  • 5

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

    删除 var propAsObject = Expression.Convert(property, typeof(object)); didn 't work with properties that were value types, such as integer, as it wouldn' t隐式将int封装到object .

    使用来自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)
                )
            );
    }
    }
    
  • 48

    是的,我认为除了反思之外还有另一种方式 .

    例:

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

    试图回忆起我的头脑中的确切语法,但我认为这是正确的 .

  • 1

    反思就是答案!

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

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

  • 1

    你可以使用动态Linq - 查看this博客 .

    另请查看this StackOverFlow帖子...

  • 95

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

    var query = query
              .Where("Category.CategoryName == @0 and Orders.Count >= @1", "Book", 10)
              .OrderBy("ProductId")
              .Select("new(ProductName as Name, Price)");
    
  • 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"));
    

相关问题