简体   繁体   English

IEnumerable 上的动态 LINQ OrderBy<t> / 可查询<t></t></t>

[英]Dynamic LINQ OrderBy on IEnumerable<T> / IQueryable<T>

I found an example in the VS2008 Examples for Dynamic LINQ that allows you to use a SQL-like string (eg OrderBy("Name, Age DESC")) for ordering.我在VS2008 Examples for Dynamic LINQ 中找到了一个示例,它允许您使用类似 SQL 的字符串(例如OrderBy("Name, Age DESC"))进行排序。 Unfortunately, the method included only works on IQueryable<T> .不幸的是,包含的方法仅适用于IQueryable<T> Is there any way to get this functionality on IEnumerable<T> ?有没有办法在IEnumerable<T>上获得此功能?

Just stumbled into this oldie...刚刚偶然发现了这个老歌......

To do this without the dynamic LINQ library, you just need the code as below.要在没有动态 LINQ 库的情况下执行此操作,您只需要如下代码。 This covers most common scenarios including nested properties.这涵盖了最常见的场景,包括嵌套属性。

To get it working with IEnumerable<T> you could add some wrapper methods that go via AsQueryable - but the code below is the core Expression logic 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 dynamic only applies to LINQ-to-Objects (expression-trees for ORMs etc can't really represent dynamic queries - MemberExpression doesn't support it).编辑:如果你想将它与dynamic混合会变得更有趣 - 尽管请注意dynamic仅适用于 LINQ 到对象(ORM 等的表达式树不能真正代表dynamic查询 - MemberExpression不支持它)。 But here's a way to do it with LINQ-to-Objects.但是这里有一种方法可以使用 LINQ-to-Objects 来做到这一点。 Note that the choice of Hashtable is due to favorable locking semantics:请注意,选择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);
        }
    }
}

Too easy without any complication:太简单了,没有任何复杂性:

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

Edit : to save some time, the System.Linq.Dynamic.Core (System.Linq.Dynamic is deprecated) assembly is not part of the framework, but can be installed from nuget: System.Linq.Dynamic.Core编辑:为了节省一些时间, System.Linq.Dynamic.Core (不推荐使用 System.Linq.Dynamic)程序集不是框架的一部分,但可以从 nuget 安装: System.Linq.Dynamic.Core

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,然后对它运行动态订单。

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

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.请注意,使用反射比直接访问属性要慢得多,因此必须调查性能。

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

I was trying to do this but having problems with Kjetil Watnedal's solution because I don't use the inline linq syntax - I prefer method-style syntax.我试图这样做,但是Kjetil Watnedal 的解决方案有问题,因为我不使用内联 linq 语法 - 我更喜欢方法风格的语法。 My specific problem was in trying to do dynamic sorting using a custom IComparer .我的具体问题是尝试使用自定义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 OrderBy to use custom IComparer - because I wanted to do Natural sorting .最后一件事 - 我提到我希望OrderBy使用自定义IComparer - 因为我想做自然排序

To do that, I just alter the OrderBy to:为此,我只需将OrderBy更改为:

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

See this post for the code for NaturalSortComparer() .有关NaturalSortComparer()的代码,请参阅此帖子

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

Use dynamic linq使用动态linq

just add using System.Linq.Dynamic;只需添加using System.Linq.Dynamic;

And use it like this to order all your columns:并像这样使用它来订购所有列:

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

After a lot of searching this worked for me:经过大量搜索,这对我有用:

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

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

您可以将 IEnumerable 转换为 IQueryable。

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

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 GetPropertyValue function 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 以供执行。

First Install Dynamic Tools --> NuGet Package Manager --> Package Manager Console首先安装动态工具 --> NuGet 包管理器 --> 包管理器控制台

install-package System.Linq.Dynamic

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

Now you can use OrderBy("Name, Age DESC")现在您可以使用OrderBy("Name, Age DESC")

Thanks to Maarten ( Query a collection using PropertyInfo object in LINQ ) I got this solution:感谢 Maarten( 使用 LINQ 中的 PropertyInfo 对象查询集合)我得到了这个解决方案:

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

In my case I was working on a "ColumnHeaderMouseClick" (WindowsForm) so just found the specific Column pressed and its correspondent PropertyInfo:就我而言,我正在处理“ColumnHeaderMouseClick”(WindowsForm),因此刚刚找到按下的特定列及其对应的 PropertyInfo:

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

OR或者

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

(be sure to have your column Names matching the object Properties) (确保您的列名称与对象属性匹配)

Cheers干杯

This answer is a response to the comments that need an example for the solution provided by @John Sheehan - Runscope此答案是对@John Sheehan - Runscope提供的解决方案需要示例的评论的回应

Please provide an example for the rest of us.请为我们其他人提供一个例子。

in DAL (Data Access Layer),在 DAL(数据访问层)中,

The IEnumerable version: IEnumerable 版本:

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

    return orders  // IEnumerable<Order>
}

The IQueryable version IQueryable 版本

public IQueryable<Order> GetOrdersAsQuerable()
{
    IEnumerable<Order> qry= GetOrders();

    // use the built-in extension method  AsQueryable in  System.Linq namespace
    return qry.AsQueryable();            
}

Now you can use the IQueryable version to bind, for example GridView in Asp.net and benefit for sorting (you can't sort using IEnumerable version)现在你可以使用 IQueryable 版本来绑定,例如 Asp.net 中的 GridView 并有利于排序(你不能使用 IEnumerable 版本进行排序)

I used Dapper as ORM and build IQueryable version and utilized sorting in GridView in asp.net so easy.我使用 Dapper 作为 ORM 并构建 IQueryable 版本,并在 asp.net 的 GridView 中使用排序非常简单。

You can use this:你可以使用这个:

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

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

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

you can do it like this for multiple order by您可以通过以下方式为多个订单执行此操作

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

You can define a dictionary from string to Func<> like this :您可以像这样定义从字符串到 Func<> 的字典:

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

And use it like this :并像这样使用它:

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

In this case you can dynamically sort by string.在这种情况下,您可以按字符串动态排序。

If you are using Specification (such as Ardalis Specification )如果您使用规范(例如Ardalis Specification

using Microsoft.EntityFrameworkCore;

namespace TestExtensions;

public static class IQueryableExtensions
{
    public static void ApplyOrder<T>(ISpecificationBuilder<T> query, string propertyName, bool ascendingOrder)
    {
        if (ascendingOrder)
            query.OrderBy(T => EF.Property<object>(T!, propertyName));
        else
            query.OrderByDescending(T => EF.Property<object>(T!, propertyName));
    }
}

With Net6 and EF使用 Net6 和 EF

.AsQueryable().OrderBy((ColumnOrder.Column, ColumnOrder.Dir));
                            

Simple questions should have simple answers - all you need are these two extension methods:简单的问题应该有简单的答案——你只需要这两种扩展方法:

public static IOrderedEnumerable<TSource> OrderByDynamic<TSource, TKey>(this IEnumerable<TSource> source, bool isDescending, Func<TSource, TKey> keySelector) {
    if (isDescending) {
         return source.OrderByDescending<TSource, TKey>(keySelector);
    } else {
         return source.OrderBy<TSource, TKey>(keySelector);
    }
}

public static IOrderedEnumerable<TSource> OrderByDynamic<TSource, TKey>(this IEnumerable<TSource> source, bool isDescending, Func<TSource, TKey> keySelector, IComparer<TKey> comparer) {
    if (isDescending) {
         return source.OrderByDescending<TSource, TKey>(keySelector,comparer);
    } else {
         return source.OrderBy<TSource, TKey>(keySelector,comparer);
    }
}

Usage:用法:

var orderedList = UnorderedList.OrderByDynamic(isDescending, x => x.MySortValue).ToList();

I am able to do this with the code below.我可以使用下面的代码来做到这一点。 No need write long and complex code.无需编写冗长复杂的代码。

 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();
            }
        }
var result1 = lst.OrderBy(a=>a.Name);// for ascending order. 
 var result1 = lst.OrderByDescending(a=>a.Name);// for desc order. 

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM