代码之家  ›  专栏  ›  技术社区  ›  John Sheehan

IEnumerable上的动态Linq orderby<t>/iQueryable<t>

  •  633
  • John Sheehan  · 技术社区  · 16 年前

    我在 VS2008 Examples 对于允许您使用类似SQL的字符串(例如 OrderBy("Name, Age DESC")) 订购。不幸的是,该方法仅适用于 IQueryable<T> ;有什么方法可以启用这个功能吗 IEnumerable<T> ?

    19 回复  |  直到 6 年前
        1
  •  864
  •   Dima    7 年前

    只是偶然发现了这个老东西…

    要在没有动态Linq库的情况下完成这项工作,您只需要下面的代码。这涵盖了最常见的场景,包括嵌套属性。

    让它与 IEnumerable<T> 你可以添加一些包装方法 AsQueryable -但下面的代码是核心 Expression 需要逻辑。

    public static IOrderedQueryable<T> OrderBy<T>(
        this IQueryable<T> source, 
        string property)
    {
        return ApplyOrder<T>(source, property, "OrderBy");
    }
    
    public static IOrderedQueryable<T> OrderByDescending<T>(
        this IQueryable<T> source, 
        string property)
    {
        return ApplyOrder<T>(source, property, "OrderByDescending");
    }
    
    public static IOrderedQueryable<T> ThenBy<T>(
        this IOrderedQueryable<T> source, 
        string property)
    {
        return ApplyOrder<T>(source, property, "ThenBy");
    }
    
    public static IOrderedQueryable<T> ThenByDescending<T>(
        this IOrderedQueryable<T> source, 
        string property)
    {
        return ApplyOrder<T>(source, property, "ThenByDescending");
    }
    
    static IOrderedQueryable<T> ApplyOrder<T>(
        IQueryable<T> source, 
        string property, 
        string methodName) 
    {
        string[] props = property.Split('.');
        Type type = typeof(T);
        ParameterExpression arg = Expression.Parameter(type, "x");
        Expression expr = arg;
        foreach(string prop in props) {
            // use reflection (not ComponentModel) to mirror LINQ
            PropertyInfo pi = type.GetProperty(prop);
            expr = Expression.Property(expr, pi);
            type = pi.PropertyType;
        }
        Type delegateType = typeof(Func<,>).MakeGenericType(typeof(T), type);
        LambdaExpression lambda = Expression.Lambda(delegateType, expr, arg);
    
        object result = typeof(Queryable).GetMethods().Single(
                method => method.Name == methodName
                        && method.IsGenericMethodDefinition
                        && method.GetGenericArguments().Length == 2
                        && method.GetParameters().Length == 2)
                .MakeGenericMethod(typeof(T), type)
                .Invoke(null, new object[] {source, lambda});
        return (IOrderedQueryable<T>)result;
    }
    

    编辑:如果你想把它和 dynamic -尽管请注意 动态 仅适用于对象的Linq(窗体等的表达式树不能真正表示 动态 查询- MemberExpression 不支持它)。但这里有一种使用linq-to对象的方法。注意,选择 Hashtable 由于良好的锁语义:

    using Microsoft.CSharp.RuntimeBinder;
    using System;
    using System.Collections;
    using System.Collections.Generic;
    using System.Dynamic;
    using System.Linq;
    using System.Runtime.CompilerServices;
    static class Program
    {
        private static class AccessorCache
        {
            private static readonly Hashtable accessors = new Hashtable();
    
            private static readonly Hashtable callSites = new Hashtable();
    
            private static CallSite<Func<CallSite, object, object>> GetCallSiteLocked(
                string name) 
            {
                var callSite = (CallSite<Func<CallSite, object, object>>)callSites[name];
                if(callSite == null)
                {
                    callSites[name] = callSite = CallSite<Func<CallSite, object, object>>
                        .Create(Binder.GetMember(
                                    CSharpBinderFlags.None, 
                                    name, 
                                    typeof(AccessorCache),
                                    new CSharpArgumentInfo[] { 
                                        CSharpArgumentInfo.Create(
                                            CSharpArgumentInfoFlags.None, 
                                            null) 
                                    }));
                }
                return callSite;
            }
    
            internal static Func<dynamic,object> GetAccessor(string name)
            {
                Func<dynamic, object> accessor = (Func<dynamic, object>)accessors[name];
                if (accessor == null)
                {
                    lock (accessors )
                    {
                        accessor = (Func<dynamic, object>)accessors[name];
                        if (accessor == null)
                        {
                            if(name.IndexOf('.') >= 0) {
                                string[] props = name.Split('.');
                                CallSite<Func<CallSite, object, object>>[] arr 
                                    = Array.ConvertAll(props, GetCallSiteLocked);
                                accessor = target =>
                                {
                                    object val = (object)target;
                                    for (int i = 0; i < arr.Length; i++)
                                    {
                                        var cs = arr[i];
                                        val = cs.Target(cs, val);
                                    }
                                    return val;
                                };
                            } else {
                                var callSite = GetCallSiteLocked(name);
                                accessor = target =>
                                {
                                    return callSite.Target(callSite, (object)target);
                                };
                            }
                            accessors[name] = accessor;
                        }
                    }
                }
                return accessor;
            }
        }
    
        public static IOrderedEnumerable<dynamic> OrderBy(
            this IEnumerable<dynamic> source, 
            string property)
        {
            return Enumerable.OrderBy<dynamic, object>(
                source, 
                AccessorCache.GetAccessor(property), 
                Comparer<object>.Default);
        }
    
        public static IOrderedEnumerable<dynamic> OrderByDescending(
            this IEnumerable<dynamic> source, 
            string property)
        {
            return Enumerable.OrderByDescending<dynamic, object>(
                source, 
                AccessorCache.GetAccessor(property), 
                Comparer<object>.Default);
        }
    
        public static IOrderedEnumerable<dynamic> ThenBy(
            this IOrderedEnumerable<dynamic> source, 
            string property)
        {
            return Enumerable.ThenBy<dynamic, object>(
                source, 
                AccessorCache.GetAccessor(property), 
                Comparer<object>.Default);
        }
    
        public static IOrderedEnumerable<dynamic> ThenByDescending(
            this IOrderedEnumerable<dynamic> source, 
            string property)
        {
            return Enumerable.ThenByDescending<dynamic, object>(
                source, 
                AccessorCache.GetAccessor(property), 
                Comparer<object>.Default);
        }
    
        static void Main()
        {
            dynamic a = new ExpandoObject(), 
                    b = new ExpandoObject(), 
                    c = new ExpandoObject();
            a.X = "abc";
            b.X = "ghi";
            c.X = "def";
            dynamic[] data = new[] { 
                new { Y = a },
                new { Y = b }, 
                new { Y = c } 
            };
    
            var ordered = data.OrderByDescending("Y.X").ToArray();
            foreach (var obj in ordered)
            {
                Console.WriteLine(obj.Y.X);
            }
        }
    }
    
        2
  •  216
  •   poke    9 年前

    太容易了,没有任何并发症:

    1. 添加 using System.Linq.Dynamic; 在顶部。
    2. 使用 vehicles = vehicles.AsQueryable().OrderBy("Make ASC, Year DESC").ToList();
        3
  •  77
  •   John Sheehan    13 年前

    我找到了答案。我可以使用 .AsQueryable<>() 扩展方法将我的列表转换为iqueryable,然后针对它运行动态顺序。

        4
  •  50
  •   Adam Anderson    15 年前

    刚刚偶然发现这个问题。

    使用上面Marc的applyOrder实现,我将处理类似SQL的字符串的扩展方法组合在一起,比如:

    list.OrderBy("MyProperty DESC, MyOtherProperty ASC");
    

    详情见: http://aonnull.blogspot.com/2010/08/dynamic-sql-like-linq-orderby-extension.html

        5
  •  39
  •   Kjetil Watnedal    16 年前

    我想使用反射来获得你想要排序的任何属性都是可行的:

    IEnumerable<T> myEnumerables
    var query=from enumerable in myenumerables
              where some criteria
              orderby GetPropertyValue(enumerable,"SomeProperty")
              select enumerable
    
    private static object GetPropertyValue(object obj, string property)
    {
        System.Reflection.PropertyInfo propertyInfo=obj.GetType().GetProperty(property);
        return propertyInfo.GetValue(obj, null);
    }
    

    请注意,使用反射比直接访问属性慢得多,因此必须研究性能。

        6
  •  18
  •   poke    9 年前

    只是建立在别人所说的基础上。我发现下面的方法很有效。

    public static IEnumerable<T> OrderBy<T>(this IEnumerable<T> input, string queryString)
    {
        if (string.IsNullOrEmpty(queryString))
            return input;
    
        int i = 0;
        foreach (string propname in queryString.Split(','))
        {
            var subContent = propname.Split('|');
            if (Convert.ToInt32(subContent[1].Trim()) == 0)
            {
                if (i == 0)
                    input = input.OrderBy(x => GetPropertyValue(x, subContent[0].Trim()));
                else
                    input = ((IOrderedEnumerable<T>)input).ThenBy(x => GetPropertyValue(x, subContent[0].Trim()));
            }
            else
            {
                if (i == 0)
                    input = input.OrderByDescending(x => GetPropertyValue(x, subContent[0].Trim()));
                else
                    input = ((IOrderedEnumerable<T>)input).ThenByDescending(x => GetPropertyValue(x, subContent[0].Trim()));
            }
            i++;
        }
    
        return input;
    }
    
        7
  •  11
  •   Shimmy Weitzhandler 500 - Internal Server Error    10 年前

    我在寻找linq multiple orderby子句时遇到了这个问题。 也许这就是作者想要的

    以下是如何做到这一点:

    var query = pets.OrderBy(pet => pet.Name).ThenByDescending(pet => pet.Age);    
    
        8
  •  9
  •   Cœur Gustavo Armenta    6 年前

    我本来想这么做,但有问题 Kjetil Watnedal's solution 因为我不使用内联LINQ语法-我更喜欢方法样式语法。我的具体问题是尝试使用自定义的 IComparer .

    我的解决方案是这样的:

    给出这样一个IQueryable查询:

    List<DATA__Security__Team> teams = TeamManager.GetTeams();
    var query = teams.Where(team => team.ID < 10).AsQueryable();
    

    并给出一个运行时排序字段参数:

    string SortField; // Set at run-time to "Name"
    

    动态orderby如下所示:

    query = query.OrderBy(item => item.GetReflectedPropertyValue(SortField));
    

    它使用了一个名为getReflectedPropertyValue()的小助手方法:

    public static string GetReflectedPropertyValue(this object subject, string field)
    {
        object reflectedValue = subject.GetType().GetProperty(field).GetValue(subject, null);
        return reflectedValue != null ? reflectedValue.ToString() : "";
    }
    

    最后一件事-我提到我想要 OrderBy 使用习惯 排序次序 -因为我想这么做 Natural sorting .

    为此,我只需更改 排序 到:

    query = query.OrderBy(item => item.GetReflectedPropertyValue(SortField), new NaturalSortComparer<string>());
    

    this post 为代码 NaturalSortComparer() .

        9
  •  4
  •   Community CDub    8 年前

    您可以添加它:

    public static IEnumerable<T> OrderBy( this IEnumerable<T> input, string queryString) {
        //parse the string into property names
        //Use reflection to get and sort by properties
        //something like
    
        foreach( string propname in queryString.Split(','))
            input.OrderBy( x => GetPropertyValue( x, propname ) );
    
        // I used Kjetil Watnedal's reflection example
    }
    

    这个 GetPropertyValue 函数来自 Kjetil Watnedal's answer

    问题是为什么?任何这样的类型都会在运行时抛出异常,而不是在编译时抛出异常(如d2viant的答案)。

    如果您处理的是linq to sql,而orderby是一个表达式树,那么它将转换为sql以供执行。

        10
  •  4
  •   Sameer Alibhai    15 年前

    这里还有一些我觉得有趣的东西。 如果源是数据表,则可以使用动态排序而不使用动态LINQ

    DataTable orders = dataSet.Tables["SalesOrderHeader"];
    EnumerableRowCollection<DataRow> query = from order in orders.AsEnumerable()
                                             orderby order.Field<DateTime>("OrderDate")
                                             select order;
    DataView view = query.AsDataView();
    bindingSource1.DataSource = view;
    

    参考文献: http://msdn.microsoft.com/en-us/library/bb669083.aspx (使用数据集扩展)

    通过将其转换为数据视图,还有一种方法可以做到这一点:

    DataTable contacts = dataSet.Tables["Contact"];    
    DataView view = contacts.AsDataView();    
    view.Sort = "LastName desc, FirstName asc";    
    bindingSource1.DataSource = view;
    dataGridView1.AutoResizeColumns();
    
        11
  •  4
  •   Community CDub    8 年前

    多亏了马丁( Query a collection using PropertyInfo object in LINQ )我得到了这个解决方案:

    myList.OrderByDescending(x => myPropertyInfo.GetValue(x, null)).ToList();
    

    在我的例子中,我正在处理一个“ColumnHeaderMouseClick”(WindowsForm),所以刚找到按下的特定列及其对应的属性信息:

    foreach (PropertyInfo column in (new Process()).GetType().GetProperties())
    {
        if (column.Name == dgvProcessList.Columns[e.ColumnIndex].Name)
        {}
    }
    

    PropertyInfo column = (new Process()).GetType().GetProperties().Where(x => x.Name == dgvProcessList.Columns[e.ColumnIndex].Name).First();
    

    (请确保列名与对象属性匹配)

    干杯

        12
  •  4
  •   nawfal Donny V.    11 年前

    经过大量的搜索,这对我很有用:

    public static IEnumerable<TEntity> OrderBy<TEntity>(this IEnumerable<TEntity> source, 
                                                        string orderByProperty, bool desc)
    {
        string command = desc ? "OrderByDescending" : "OrderBy";
        var type = typeof(TEntity);
        var property = type.GetProperty(orderByProperty);
        var parameter = Expression.Parameter(type, "p");
        var propertyAccess = Expression.MakeMemberAccess(parameter, property);
        var orderByExpression = Expression.Lambda(propertyAccess, parameter);
        var resultExpression = Expression.Call(typeof(Queryable), command, 
                                               new[] { type, property.PropertyType },
                                               source.AsQueryable().Expression, 
                                               Expression.Quote(orderByExpression));
        return source.AsQueryable().Provider.CreateQuery<TEntity>(resultExpression);
    }
    
        13
  •  4
  •   Richard YS    11 年前

    可以将IEnumerable转换为IQueryable。

    items = items.AsQueryable().OrderBy("Name ASC");
    
        14
  •  2
  •   Mike Christiansen    16 年前

    另一种解决方案使用以下类/接口。它不是真正的动态的,但它是有效的。

    public interface IID
    {
        int ID
        {
            get; set;
        }
    }
    
    public static class Utils
    {
        public static int GetID<T>(ObjectQuery<T> items) where T:EntityObject, IID
        {
            if (items.Count() == 0) return 1;
            return items.OrderByDescending(u => u.ID).FirstOrDefault().ID + 1;
        }
    }
    
        15
  •  1
  •   M.Hassan    7 年前

    此答案是对需要提供解决方案示例的注释的响应 @John Sheehan - Runscope

    请为我们其他人提供一个例子。

    在DAL(数据访问层)中,

    IEnumerable版本:

      public  IEnumerable<Order> GetOrders()
        {
          // i use Dapper to return IEnumerable<T> using Query<T>
          //.. do stuff
          return  orders  // IEnumerable<Order>
      }
    

    可查询版本

      public IQueryable<Order> GetOrdersAsQuerable()
        {
            IEnumerable<Order> qry= GetOrders();
            //use the built-in extension method  AsQueryable in  System.Linq namespace
            return qry.AsQueryable();            
        }
    

    现在您可以使用iQuery版本进行绑定,例如ASP.NET中的GridView和排序优势(不能使用IEnumerable版本进行排序)

    我使用dapper作为ORM,构建了iqueryable版本,并在ASP.NET中轻松地使用了GridView中的排序。

        16
  •  1
  •   Aminur Rahman    7 年前

    第一次安装动态 工具-->Nuget Package Manager-->Package Manager控制台

    install-package System.Linq.Dynamic
    

    添加 命名空间 using System.Linq.Dynamic;

    现在你可以使用 OrderBy("Name, Age DESC")

        17
  •  0
  •   user145610    12 年前

    将list转换为ienumerable或iquerable,使用system.linq.dynamic命名空间添加,然后可以将属性名用逗号分隔的字符串提到orderby方法,该方法默认来自system.linq.dynamic。

        18
  •  0
  •   Masoud Darvishian    6 年前

    使用动态 linq

    只要添加 using System.Linq.Dynamic;

    然后像这样对所有列进行排序:

    string sortTypeStr = "ASC"; // or DESC
    string SortColumnName = "Age"; // Your column name
    query = query.OrderBy($"{SortColumnName} {sortTypeStr}");
    
        19
  •  -3
  •   Arindam    9 年前
    var result1 = lst.OrderBy(a=>a.Name);// for ascending order. 
     var result1 = lst.OrderByDescending(a=>a.Name);// for desc order.