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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/41244/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-03 09:50:07  来源:igfitidea点击:

Dynamic LINQ OrderBy on IEnumerable<T> / IQueryable<T>

提问by John Sheehan

I found an example in the VS2008 Examplesfor Dynamic LINQ that allows you to use a sql-like string (e.g. OrderBy("Name, Age DESC"))for ordering. Unfortunately, the method included only works on IQueryable<T>. Is there any way to get this functionality on IEnumerable<T>?

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

采纳答案by Marc Gravell

Just stumbled into this oldie...

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

To do this without the dynamic LINQ library, you just need the code as below. This covers most common scenarios including nested properties.

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

To get it working with IEnumerable<T>you could add some wrapper methods that go via AsQueryable- but the code below is the core Expressionlogic needed.

为了让它与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;
}


Edit: it gets more fun if you want to mix that with dynamic- although note that dynamiconly applies to LINQ-to-Objects (expression-trees for ORMs etc can't really represent dynamicqueries - MemberExpressiondoesn't support it). But here's a way to do it with LINQ-to-Objects. Note that the choice of Hashtableis due to favorable locking semantics:

编辑:如果你想将它与它混合会变得更有趣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);
        }
    }
}

回答by Kjetil Watnedal

I guess it would work to use reflection to get whatever property you want to sort on:

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

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

Note that using reflection is considerably slower than accessing the property directly, so the performance would have to be investigated.

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

回答by Keith

You could add it:

你可以添加它:

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
}

The GetPropertyValuefunction is from Kjetil Watnedal's answer

GetPropertyValue功能来自Kjetil Watnedal 的回答

The issue would be why? Any such sort would throw exceptions at run-time, rather than compile time (like D2VIANT's answer).

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

If you're dealing with Linq to Sql and the orderby is an expression tree it will be converted into SQL for execution anyway.

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

回答by John Sheehan

I found the answer. I can use the .AsQueryable<>()extension method to convert my list to IQueryable, then run the dynamic order by against it.

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

回答by InfoStatus

I've stumble this question looking for Linq multiple orderby clauses and maybe this was what the author was looking for

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

Here's how to do that:

以下是如何做到这一点:

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

回答by Mike Christiansen

An alternate solution uses the following class/interface. It's not truly dynamic, but it works.

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

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

回答by vdhant

Just building on what others have said. I found that the following works quite well.

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

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

回答by James McCormack

I was trying to do this but having problems with Kjetil Watnedal's solutionbecause I don't use the inline linq syntax - I prefer method-style syntax. My specific problem was in trying to do dynamic sorting using a custom IComparer.

我试图这样做,但在使用Kjetil Watnedal 的解决方案时遇到了问题,因为我不使用内联 linq 语法——我更喜欢方法风格的语法。我的具体问题是尝试使用自定义IComparer.

My solution ended up like this:

我的解决方案最终是这样的:

Given an IQueryable query like so:

给定一个 IQueryable 查询,如下所示:

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

And given a run-time sort field argument:

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

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

The dynamic OrderBy looks like so:

动态 OrderBy 看起来像这样:

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

And that's using a little helper method called GetReflectedPropertyValue():

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

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


One last thing - I mentioned that I wanted the OrderByto use custom IComparer- because I wanted to do Natural sorting.

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

To do that, I just alter the OrderByto:

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

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

See this postfor the code for NaturalSortComparer().

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

回答by Sameer Alibhai

Here's something else I found interesting. If your source is a DataTable, you can use dynamic sorting without using Dynamic Linq

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

reference: http://msdn.microsoft.com/en-us/library/bb669083.aspx(Using DataSetExtensions)

参考:http: //msdn.microsoft.com/en-us/library/bb669083.aspx(使用 DataSetExtensions)

Here is one more way to do it by converting it to a DataView:

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

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

回答by Adam Anderson

Just stumbled across this question.

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

Using Marc's ApplyOrder implementation from above, I slapped together an Extension method that handles SQL-like strings like:

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

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

Details can be found here: http://aonnull.blogspot.com/2010/08/dynamic-sql-like-linq-orderby-extension.html

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