IEnumerable <T> / IQueryable <T>上的动态LINQ OrderBy


668

我在VS2008动态LINQ 示例中找到了一个示例,该示例允许您使用类似sql的字符串(例如,OrderBy("Name, Age DESC"))用于订购。不幸的是,所包含的方法仅适用于IQueryable<T>该功能。是否有任何方法可以启用此功能IEnumerable<T>


1
我认为,到目前为止,最好的答案是:System.Linq.Dynamic.Core库。
Shahin Dohan

Answers:


904

刚发现这个老歌...

要在没有动态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-尽管请注意,这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);
        }
    }
}

109
我见过的最该死的代码:)刚刚解决了我项目中的一百万个问题:)
sajidnizami

4
@Dave-您需要以开始IQueryable<T>,因此,如果您有类似List<T>(是IEnumerable<T>)的内容,则可能需要使用AsQueryable()-例如var sorted = someList.AsQueryable().OrderBy("Foo.Bar");
Marc Gravell

7
您是否看到过...这可能对某些人有帮助... stackoverflow.com/questions/557819 / ... 它是一种更强大的解决方案。
anthonyv

28
@MGOwen您似乎误解了代码的本质。无论您在项目中放置40行还是在外部库中(预编译或作为源)这些40行都是相同的。这本来是相当惊人的,如果我有联系,在08月以来月'11已经存在上的NuGet库(不仅仅是因为的NuGet不存在,那么无论是),但根本“它是做什么的”,是相同。同样,您使用短语“实际解决方案”,就好像每个编码问题都有一些定义明确的一致的单一路线:没有。
马克·格雷夫

5
@MGOwen btw,外部库是2296行代码(不包括AssemblyInfo.cs);这有点让这40条线看起来很合理
Marc Gravell

231

太简单了,没有任何并发​​症:

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

11
System.Linq.Dynamic从哪里得到的?
痴呆症

1
将linq与MongoDB一起使用时也可以使用。
soupy1976

32
公认的答案可能是2008年的正确答案,但目前这是目前最简单,最正确的答案。
EL MOJO 2014年

1
这真的很好,操作简单,内部非常复杂,喜欢它
Mrinal Kamboj 2016年

5
对于“未来”的人们,如果您使用的是dotnet core,请使用:nuget.org/packages/System.Linq.Dynamic.Core
Rafael Merlin

78

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


52
请为我们其他人提供示例。
MGOwen

54

刚刚偶然发现了这个问题。

从上面使用Marc的ApplyOrder实现,我拍了个Extension方法,该方法处理类似SQL的字符串,例如:

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

可以在这里找到详细信息:http : //aonnull.blogspot.com/2010/08/dynamic-sql-like-linq-orderby-extension.html


1
好东西,只需添加如下修改即可使属性名称不区分大小写:PropertyInfo pi = type.GetProperty(prop,BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase);
Mrinal Kamboj '16

43

我想使用反射来获取要排序的任何属性都可以:

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不希望的值,但选择器兰巴/委托(Func键<TSource,TKEY的>的KeySelector)..
戴维兰德曼

2
在发布之前,我确实尝试过该示例,是的,它确实有效。
Kjetil Watnedal

3
+1这正是我想要的!这对于简单的页面排序问题很有用。
Andrew Siemer '04

这对我不起作用。我想念什么吗?什么是“ SomeProperty”。我尝试给出属性名称以及property.GetType()。我有IQueryable <>而不是IEnumerable <>
SO User

2
@Alex Shkor:您应该如何在不查看所有元素的情况下对元素进行排序?但是,在其他答案中有更好的解决方案。
Kjetil Watnedal'2

19

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

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

12

我在寻找Linq多个orderby子句时遇到了这个问题,也许这就是作者想要的东西

这样做的方法如下:

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

5
由于缺乏解释,+ 1取消了反对票。我也认为作者可能对多重排序感兴趣。即使动态关键词,也没有理由不赞成。
杰森·克莱本

11

我试图这样做,但是由于我不使用内联linq语法,所以在Kjetil Watnedal的解决方案中遇到了问题-我更喜欢方法样式的语法。我的特定问题是尝试使用自定义进行动态排序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使用custom- IComparer因为我想进行自然排序

为此,我将更OrderBy改为:

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

有关的代码,请参见此帖子NaturalSortComparer()


5

使用动态 linq

只需添加 using System.Linq.Dynamic;

像这样使用它来排序所有列:

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

4

您可以添加它:

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的回答

问题是为什么?任何此类排序都会在运行时而不是编译时引发异常(如D2VIANT的回答)。

如果您正在处理Linq to Sql,而orderby是一个表达式树,则无论如何它将被转换为SQL以执行。


将对所有元素执行GetPropertyValue方法,这是一个糟糕的解决方案。
Alex Shkor '02

2
OrderBy不要维持以前的订单!!
Amir Ismail'4

4

这是我发现有趣的事情。如果您的源是数据表,则可以使用动态排序,而无需使用动态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/zh-cn/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();

4

感谢Maarten(使用LINQ中的PropertyInfo对象查询集合),我得到了以下解决方案:

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

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

干杯


4

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

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

4

您可以将IEnumerable转换为IQueryable。

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

3

替代解决方案使用以下类/接口。它不是真正的动态,但可以。

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

2

此答案是对评论的回应,该评论需要@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>
  }

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中使用排序非常容易。


2

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

install-package System.Linq.Dynamic

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

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


我如何将其与内部属性排序一起使用-如OrderBy(“ Branch.BranchName”,“ Descending”)
devC

这对我有用。也许是因为这个问题已有10年之久,而且这种更简单的方法只是后来才出现。
kosherjellyfish

1

您可以使用此:

        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个属性进行动态排序,这就像梦一样。易于实施且无忧。
巴赞加

0

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


-3
var result1 = lst.OrderBy(a=>a.Name);// for ascending order. 
 var result1 = lst.OrderByDescending(a=>a.Name);// for desc order. 
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.