【问题标题】:Dynamic LINQ OrderBy on IEnumerable<T> / IQueryable<T>IEnumerable<T> / IQueryable<T> 上的动态 LINQ OrderBy
【发布时间】:2010-09-07 15:52:32
【问题描述】:

我在动态 LINQ 的 VS2008 Examples 中找到了一个示例,它允许您使用类似 SQL 的字符串(例如,OrderBy("Name, Age DESC")) 用于排序。不幸的是,包含的方法仅适用于 IQueryable&lt;T&gt;。有什么办法可以在IEnumerable&lt;T&gt; 上获得此功能?

【问题讨论】:

    标签: c# linq ienumerable iqueryable linq-to-objects


    【解决方案1】:

    刚刚偶然发现这个老歌......

    要在没有动态 LINQ 库的情况下执行此操作,您只需要如下代码。这涵盖了最常见的场景,包括嵌套属性。

    要使其与IEnumerable&lt;T&gt; 一起工作,您可以添加一些通过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 混合使用,它会变得更有趣 - 尽管请注意 dynamic 仅适用于 LINQ-to-Objects(ORM 等的表达式树不能真正代表 dynamic 查询- MemberExpression 不支持)。但这里有一种使用 LINQ-to-Objects 的方法。请注意,选择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);
            }
        }
    }
    

    【讨论】:

    • 我见过的最棒的一段代码 :) 刚刚解决了我项目中的一百万个问题 :)
    • @Dave - 你需要以IQueryable&lt;T&gt; 开头,所以如果你有类似List&lt;T&gt;(即IEnumerable&lt;T&gt;)的东西,你可能需要使用AsQueryable() - 例如var sorted = someList.AsQueryable().OrderBy("Foo.Bar");
    • 你见过这个吗...它可能对某些人有帮助...stackoverflow.com/questions/557819/…它是一个更强类型的解决方案。
    • @MGOwen 你似乎误解了代码的本质。无论您将 40 行放在项目中的某处,还是这些行来自(预编译或作为源)外部库中的 40 行,这 40 行都是相同的。如果我在 08 年 10 月链接到自 11 年 12 月以来就存在的 nuget 库(尤其是因为那时 nuget 也不存在),那将是相当惊人,但基本“它在做什么”是一样的。此外,您使用“实际解决方案”这个短语,就好像每个编码问题都有一些明确的商定单一途径:没有。
    • @MGOwen btw,外部库是2296行代码(不包括AssemblyInfo.cs);这让这里的 40 行看起来很合理
    【解决方案2】:

    太简单了,没有任何复杂性:

    1. 在顶部添加using System.Linq.Dynamic;
    2. 使用vehicles = vehicles.AsQueryable().OrderBy("Make ASC, Year DESC").ToList();

    编辑:为了节省一些时间,System.Linq.Dynamic.Core(System.Linq.Dynamic 已弃用)程序集不是框架的一部分,而是可以从 nuget 安装:System.Linq.Dynamic.Core

    【讨论】:

    • 你从哪里得到System.Linq.Dynamic
    • 在 MongoDB 中使用 linq 时也可以使用。
    • 接受的答案可能是 2008 年的正确答案,但目前这是最简单、最正确的答案。
    • 对于“未来”的人们,如果您使用的是dotnet core,请使用:nuget.org/packages/System.Linq.Dynamic.Core
    • @RafaelMerlin 现在命名空间是 System.Linq.Dynamic.Core
    【解决方案3】:

    偶然发现了这个问题。

    使用上面 Marc 的 ApplyOrder 实现,我拼凑了一个处理类似 SQL 的字符串的扩展方法,例如:

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

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

    【讨论】:

    • 好东西,只需添加如下修改以使属性名称不区分大小写:PropertyInfo pi = type.GetProperty(prop,BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase);
    【解决方案4】:

    我想使用反射来获取您想要排序的任何属性都会起作用:

    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);
    }
    

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

    【讨论】:

    • 这还能用吗? orderby 不想要一个值,而是一个选择器 lamba/delegate (Func keySelector)..
    • 我在发布之前尝试过这个示例,是的,它确实有效。
    • +1 这正是我想要的!这对于简单的页面排序问题非常有用。
    • 这对我不起作用。我错过了什么吗? “SomeProperty”应该是什么。我尝试给出属性名称以及 property.GetType()。我有 IQueryable 而不是 IEnumerable
    • @Alex Shkor:你应该如何在不查看所有元素的情况下对元素进行排序?但是,其他答案中有更好的解决方案。
    【解决方案5】:

    只是建立在其他人所说的基础上。我发现下面的效果很好。

    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;
    }
    

    【讨论】:

      【解决方案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 使用自定义 IComparer - 因为我想做 Natural sorting

      为此,我只需将OrderBy 更改为:

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

      请参阅this post 以获取NaturalSortComparer() 的代码。

      【讨论】:

        【解决方案7】:

        我在寻找 Linq 多个 orderby 子句时偶然发现了这个问题 也许这就是作者要找的东西

        这是如何做到这一点的:

        var query = pets.OrderBy(pet => pet.Name).ThenByDescending(pet => pet.Age);    
        

        【讨论】:

        • +1 由于缺乏解释而取消了反对票。我还认为作者可能对多次订购感兴趣。即使动态关键词,也没有理由拒绝投票。
        【解决方案8】:

        使用动态linq

        只需添加using System.Linq.Dynamic;

        并像这样使用它来对所有列进行排序:

        string sortTypeStr = "ASC"; // or DESC
        string SortColumnName = "Age"; // Your column name
        query = query.OrderBy($"{SortColumnName} {sortTypeStr}");
        

        【讨论】:

          【解决方案9】:

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

          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);
          }
          

          【讨论】:

          • 我想使用的确切解决方案...
          【解决方案10】:

          你可以添加它:

          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 来执行。

          【讨论】:

          • GetPropertyValue 方法将对所有元素执行,这是不好的解决方案。
          • OrderBy不要维持之前的顺序!!
          【解决方案11】:

          这里还有一些我觉得很有趣的东西。 如果您的源是 DataTable,您可以使用动态排序而不使用 Dynamic 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(使用 DataSetExtensions)

          这是另一种将其转换为 DataView 的方法:

          DataTable contacts = dataSet.Tables["Contact"];    
          DataView view = contacts.AsDataView();    
          view.Sort = "LastName desc, FirstName asc";    
          bindingSource1.DataSource = view;
          dataGridView1.AutoResizeColumns();
          

          【讨论】:

            【解决方案12】:

            您可以将 IEnumerable 转换为 IQueryable。

            items = items.AsQueryable().OrderBy("Name ASC");
            

            【讨论】:

              【解决方案13】:

              首次安装动态 工具 --> NuGet 包管理器 --> 包管理器控制台

              install-package System.Linq.Dynamic
              

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

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

              【讨论】:

              • 如何将它与内部属性排序一起使用 - 例如 OrderBy("Branch.BranchName","Descending")
              • 这对我有用。也许是因为这个问题是 10 年前的问题,而这种更简单的方法是后来才出现的。
              【解决方案14】:

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

              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】:

                感谢 Maarten (Query a collection using PropertyInfo object in LINQ) 我得到了这个解决方案:

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

                在我的情况下,我正在处理“ColumnHeaderMouseClick”(WindowsForm),所以刚刚找到按下的特定列及其对应的 PropertyInfo:

                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();
                

                (确保您的列名称与对象属性匹配)

                干杯

                【讨论】:

                  【解决方案16】:

                  你可以用这个:

                          public List<Book> Books(string orderField, bool desc, int skip, int take)
                  {
                      var propertyInfo = typeof(Book).GetProperty(orderField);
                  
                      return _context.Books
                          .Where(...)
                          .OrderBy(p => !desc ? propertyInfo.GetValue(p, null) : 0)
                          .ThenByDescending(p => desc ? propertyInfo.GetValue(p, null) : 0)
                          .Skip(skip)
                          .Take(take)
                          .ToList();
                  }
                  

                  【讨论】:

                  • 几年后,我偶然发现了这一点;这对我有用,就像做梦一样。我对 1 到 3 个属性进行了动态排序,这就像做梦一样。易于实施且无忧。
                  • 喜欢这个答案,但是如果我需要按子类的属性进行排序,我该怎么做?
                  【解决方案17】:

                  这个答案是对需要@John Sheehan - Runscope提供的解决方案示例的cmets的回应

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

                  在 DAL(数据访问层)中,

                  IEnumerable 版本:

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

                  IQueryable 版本

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

                  现在可以使用 IQueryable 版本进行绑定,例如 Asp.net 中的 GridView 并有利于排序(不能使用 IEnumerable 版本进行排序)

                  我使用 Dapper 作为 ORM 并构建 IQueryable 版本并在 asp.net 中的 GridView 中使用排序非常简单。

                  【讨论】:

                    【解决方案18】:

                    将 List 转换为 IEnumerable 或 Iquerable,使用 System.LINQ.Dynamic 命名空间添加,然后您可以将逗号分隔字符串中的属性名称提及到默认来自 System.LINQ.Dynamic 的 OrderBy 方法。

                    【讨论】:

                      【解决方案19】:

                      你可以这样处理多个订单

                      IOrderedEnumerable<JToken> sort;
                      
                      if (query.OrderBys[0].IsDESC)
                      {
                          sort = jarry.OrderByDescending(r => (string)r[query.OrderBys[0].Key]);
                      }
                      else
                      {
                          sort = jarry.OrderBy(r =>
                              (string) r[query.OrderBys[0].Key]); 
                      }
                      
                      foreach (var item in query.OrderBys.Skip(1))
                      {
                          if (item.IsDESC)
                          {
                              sort = sort.ThenByDescending(r => (string)r[item.Key]);
                          }
                          else
                          {
                              sort = sort.ThenBy(r => (string)r[item.Key]);
                          }
                      }
                      

                      【讨论】:

                        【解决方案20】:

                        您可以像这样定义从字符串到 Func 的字典:

                        Dictionary<string, Func<Item, object>> SortParameters = new Dictionary<string, Func<Item, object>>()
                        {
                            {"Rank", x => x.Rank}
                        };
                        

                        并像这样使用它:

                        yourList.OrderBy(SortParameters["Rank"]);
                        

                        在这种情况下,您可以按字符串动态排序。

                        【讨论】:

                          【解决方案21】:

                          我可以用下面的代码做到这一点。无需编写冗长复杂的代码。

                           protected void sort_array(string field_name, string asc_desc)
                                  {
                          
                                      objArrayList= Sort(objArrayList, field_name, asc_desc);
                                  }
                          
                                  protected List<ArrayType> Sort(List<ArrayType> input, string property, string asc_desc)
                                  {
                                      if (asc_desc == "ASC")
                                      {
                          
                                          return input.OrderBy(p => p.GetType()
                                                                     .GetProperty(property)
                                                                     .GetValue(p, null)).ToList();
                                      }
                                      else
                                      {
                                          return input.OrderByDescending(p => p.GetType()
                                                                         .GetProperty(property)
                                                                         .GetValue(p, null)).ToList();
                                      }
                                  }
                          

                          【讨论】:

                            【解决方案22】:
                            var result1 = lst.OrderBy(a=>a.Name);// for ascending order. 
                             var result1 = lst.OrderByDescending(a=>a.Name);// for desc order. 
                            

                            【讨论】:

                              猜你喜欢
                              • 2023-03-14
                              • 1970-01-01
                              • 1970-01-01
                              • 2011-04-05
                              • 1970-01-01
                              • 2012-06-23
                              相关资源
                              最近更新 更多