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

Joh*_*han 657 c# linq linq-to-objects

我在动态LINQ 的VS2008示例中找到了一个示例,它允许您使用类似sql的字符串(例如,OrderBy("Name, Age DESC"))用于排序.不幸的是,包含的方法仅适用于IQueryable<T>;.有没有办法获得此功能IEnumerable<T>

Mar*_*ell 890

只是偶然发现这个老人......

要在没有动态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;
}
Run Code Online (Sandbox Code Playgroud)

编辑:如果你想混合使用它会变得更有趣dynamic- 虽然注意dynamic只适用于LINQ-to-Objects(ORMs的表达式树等不能真正代表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);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 我见过的那段该死的代码:)刚刚在我的项目中解决了一百万个问题:) (107认同)
  • @MGOwen你似乎误解了代码的本质.无论是你在项目中放置的40行,还是在外部库中这些行(预编译或源代码)都是40行是相同的.如果我在08年10月将自2011年12月以来存在的nuget图书馆链接起来(尤其是因为nuget当时也不存在),那将是非常惊人的*,但基本的"它在做什么" "是一样的.此外,您使用短语"实际解决方案",好像每个编码问题都有一些明确定义的单一路由:没有. (26认同)
  • 是否有使用此代码的查询示例? (8认同)
  • 你有没有看到这个...它可能会帮助一些人...... http://stackoverflow.com/questions/557819/strongly-typed-dynamic-linq-sorting/2794039#2794039这是一个更强类型的解决方案. (6认同)
  • @MGOwen btw,外部lib是2296行代码(不包括AssemblyInfo.cs); 这有40条线在这里看起来很合理 (5认同)
  • @Dave - 你需要从`IQueryable <T>`开始,所以如果你有像`List <T>`这样的东西(`IEnumerable <T>`),你可能需要使用`AsQueryable()` - 例如`var sorted = someList.AsQueryable().OrderBy("Foo.Bar");` (4认同)
  • 听起来很多人都使用这段代码取得了成功 - 我无法为我的生活找到如何应用它!这些是扩展方法,对吗?我该如何使用它们? (2认同)
  • 伟大的代码!你知道如何添加对"Count()"扩展方法的支持吗?调用扩展方法很安静很痛苦,而且我很难用这些方法构建表达式.我正在尝试为IQueryable <T>添加'Count()'支持,它被编码为'Queryable'类型的扩展名. (2认同)
  • @ile - `var ordered = someData.OrderBy("Name");` - 或`IEnumerable <T>`data,`var ordered = someData.AsQueryable().OrderBy("Name");` (2认同)
  • 如果有人有兴趣,我可以使用`IEnumerable`在倒数第二行中用`lambda.Compile()`代替`lambda`,除非使用`IOrderedEnumerable明显替换`IQueryable`>`IEnumerable`和`IOrderedQueryable` `.没有涉及包装 (2认同)

Ala*_*sta 224

没有任何复杂情况太容易了:

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

  • 接受的答案可能是2008年的正确答案,但目前这是最简单,最正确的答案. (30认同)
  • 你从哪里得到了`System.Linq.Dynamic`? (11认同)
  • @RafaelMerlin 命名空间现在是 System.Linq.Dynamic.Core (6认同)
  • 对于任何像我对相同解决方案所做的那样查看此问题的人 - 这适用于对“嵌套”对象进行排序,即 `vehicles.AsQueryable().OrderBy("Tire.Size").ToList();`,其中 `Tire ` 是属于 `Vehicle` 的对象。 (4认同)
  • 对于“未来”的人们,如果您使用的是dotnet core,请使用:https://www.nuget.org/packages/System.Linq.Dynamic.Core (2认同)

Joh*_*han 76

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

  • 请为我们其他人提供一个例子. (52认同)

Ada*_*son 52

偶然发现了这个问题.

使用上面的Marc的ApplyOrder实现,我把一个处理类似SQL的字符串的Extension方法拼凑在一起,如:

list.OrderBy("MyProperty DESC, MyOtherProperty ASC");
Run Code Online (Sandbox Code Playgroud)

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


Kje*_*dal 41

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

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);
}
Run Code Online (Sandbox Code Playgroud)

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

  • +1这正是我想要的!这对于简单的页面排序问题非常有用. (3认同)
  • 在发布之前,我确实尝试过该示例,是的,它确实有效。 (2认同)
  • @Alex Shkor:如何在不查看所有元素的情况下对元素进行排序?但是,在其他答案中有更好的解决方案. (2认同)

vdh*_*ant 18

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

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;
}
Run Code Online (Sandbox Code Playgroud)


Inf*_*tus 11

我绊倒了这个问题,寻找Linq多个orderby子句,也许这就是作者所寻求的

以下是如何做到这一点:

var query = pets.OrderBy(pet => pet.Name).ThenByDescending(pet => pet.Age);    
Run Code Online (Sandbox Code Playgroud)

  • 由于缺乏解释,+1取消了投票.我也认为作者可能对多个订单感兴趣.即使动态*是关键词,也没有理由拒绝投票. (5认同)

Jam*_*ack 11

我试图这样做,但遇到了Kjetil Watnedal解决方案的问题,因为我不使用内联linq语法 - 我更喜欢方法式语法.我的具体问题是尝试使用自定义进行动态排序IComparer.

我的解决方案最终如下:

给出一个像这样的IQueryable查询:

List<DATA__Security__Team> teams = TeamManager.GetTeams();
var query = teams.Where(team => team.ID < 10).AsQueryable();
Run Code Online (Sandbox Code Playgroud)

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

string SortField; // Set at run-time to "Name"
Run Code Online (Sandbox Code Playgroud)

动态OrderBy看起来像这样:

query = query.OrderBy(item => item.GetReflectedPropertyValue(SortField));
Run Code Online (Sandbox Code Playgroud)

这是使用一个名为GetReflectedPropertyValue()的辅助方法:

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

最后一件事 - 我提到我想要OrderBy使用自定义IComparer- 因为我想做自然排序.

要做到这一点,我只需OrderBy改为:

query = query.OrderBy(item => item.GetReflectedPropertyValue(SortField), new NaturalSortComparer<string>());
Run Code Online (Sandbox Code Playgroud)

请参阅此帖子以获取代码NaturalSortComparer().


Mas*_*ian 9

使用动态 linq

只需添加 using System.Linq.Dynamic;

并像这样使用它来订购所有列:

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


San*_*tos 6

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

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);
}
Run Code Online (Sandbox Code Playgroud)


小智 6

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

install-package System.Linq.Dynamic
Run Code Online (Sandbox Code Playgroud)

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

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


Kei*_*ith 5

你可以添加它:

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
}
Run Code Online (Sandbox Code Playgroud)

GetPropertyValue功能来自Kjetil Watnedal 的回答

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

如果您正在处理 Linq to Sql 并且 orderby 是一个表达式树,那么无论如何它都会被转换为 SQL 来执行。

  • `OrderBy` 不维持之前的顺序! (2认同)