Dynamic LINQ OrderBy on IEnumerable

Znalazłem przykład w VS2008 Examples dla dynamicznego LINQ, który pozwala na użycie ciągu podobnego do sql (np. OrderBy("Name, Age DESC")) do zamawiania. Niestety, dołączona metoda działa tylko na IQueryable<T>;. Czy jest jakiś sposób na włączenie tej funkcjonalności IEnumerable<T>?

Author: Robert Harvey, 2008-09-03

18 answers

Natknąłem się na tego starego...

Aby to zrobić bez dynamicznej biblioteki LINQ, wystarczy Kod jak poniżej. Obejmuje to najczęściej występujące scenariusze, w tym zagnieżdżone właściwości.

Aby go uruchomić z IEnumerable<T> możesz dodać kilka metod wrappera, które przechodzą przez AsQueryable - ale poniższy kod jest podstawową logiką 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: będzie zabawniej, jeśli chcesz mieszać to z {[5] } - chociaż zauważ, że dynamic dotyczy tylko LINQ-to-Objects (expression-trees for orms etc can 't really represent dynamic queries - MemberExpression doesn' t support it). Ale oto sposób, aby to zrobić z LINQ-to-Objects. Należy zauważyć, że wybór Hashtable wynika z korzystnej semantyki blokowania:

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);
        }
    }
}
 850
Author: Marc Gravell,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-12-11 08:52:02

Zbyt łatwe bez żadnych komplikacji:

  1. Dodaj using System.Linq.Dynamic; Na górze.
  2. Użyj vehicles = vehicles.AsQueryable().OrderBy("Make ASC, Year DESC").ToList();
 208
Author: Alaa Osta,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-12-16 09:35:50

Znalazłem odpowiedź. Mogę użyć metody rozszerzenia .AsQueryable<>(), Aby przekonwertować moją listę do IQueryable, a następnie uruchomić kolejność dynamiczną przez nią.

 77
Author: John Sheehan,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-08-30 02:25:23

Natknąłem się na to pytanie.

Używając implementacji ApplyOrder Marc z góry, połączyłem metodę rozszerzenia obsługującą ciągi podobne do SQL:

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

Szczegóły można znaleźć tutaj: http://aonnull.blogspot.com/2010/08/dynamic-sql-like-linq-orderby-extension.html

 50
Author: Adam Anderson,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-08-18 01:55:30

Myślę, że byłoby dobrze użyć refleksji, aby uzyskać jakąkolwiek nieruchomość chcesz posortować:

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

Zauważ, że używanie reflection jest znacznie wolniejsze niż bezpośrednie uzyskiwanie dostępu do właściwości, więc wydajność musi być zbadana.

 39
Author: Kjetil Watnedal,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-10-28 07:21:49

Bazując na tym, co powiedzieli inni. Okazało się, że następujące działa całkiem dobrze.

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;
}
 18
Author: vdhant,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-12-16 09:36:22

Natknąłem się na to pytanie szukając klauzul Linq multiple orderby a może właśnie tego szukał autor

Oto Jak to zrobić:

var query = pets.OrderBy(pet => pet.Name).ThenByDescending(pet => pet.Age);    
 11
Author: InfoStatus,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-04-27 21:48:37

Próbowałem to zrobić, ale mam problemy z rozwiązaniem Kjetila Watnedala ponieważ nie używam składni inline linq-wolę składnię w stylu metody. Mój szczególny problem polegał na próbie dynamicznego sortowania przy użyciu niestandardowego IComparer.

Moje rozwiązanie skończyło się tak:

Podane zapytanie IQueryable jak tak:

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

I podano argument pola sortowania w czasie wykonywania:

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

Kolejność dynamiczna wygląda tak:

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

I to za pomocą mała metoda pomocnicza o nazwie GetReflectedPropertyValue ():

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

Jeszcze jedno-wspomniałem, że chciałem, aby OrderBy używało custom IComparer - ponieważ chciałem wykonaćnaturalne sortowanie .

Aby to zrobić, zmieniam OrderBy na:

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

Zobacz ten post Po kod dla NaturalSortComparer().

 9
Author: James McCormack,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 11:47:27

Możesz dodać:

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
}

Funkcja GetPropertyValuepochodzi z odpowiedzi Kjetila Watnedala

Problemem byłoby dlaczego? Każdy taki rodzaj wyrzuca wyjątki w czasie wykonywania, a nie kompilacji (jak odpowiedź D2VIANTA).

Jeśli masz do czynienia z Linq do Sql, a orderby jest drzewem wyrażeń, to i tak zostanie przekonwertowany na SQL do wykonania.

 4
Author: Keith,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:34:37

Jest jeszcze coś, co mnie zainteresowało. Jeśli twoje źródło jest DataTable, możesz użyć sortowania dynamicznego bez użycia dynamicznego 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;

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

Jest jeszcze jeden sposób, aby to zrobić, konwertując go do DataView:

DataTable contacts = dataSet.Tables["Contact"];    
DataView view = contacts.AsDataView();    
view.Sort = "LastName desc, FirstName asc";    
bindingSource1.DataSource = view;
dataGridView1.AutoResizeColumns();
 4
Author: Sameer,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-01-13 20:01:38

Dzięki Maarten ( zapytanie kolekcji za pomocą obiektu PropertyInfo w LINQ ) otrzymałem takie rozwiązanie:

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

W moim przypadku pracowałem nad "ColumnHeaderMouseClick" (WindowsForm), więc po prostu znalazłem konkretną kolumnę wciśniętą i jej odpowiedni Właściwośćinfo:

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

Lub

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

(Upewnij się, że nazwy kolumn pasują do Właściwości obiektu)

Cheers

 4
Author: joaopintocruz,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 11:47:27

Po wielu poszukiwaniach to mi pomogło:

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
Author: Sanchitos,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-10-24 13:29:15

Możesz przekonwertować liczbę mnogą na liczbę mnogą.

items = items.AsQueryable().OrderBy("Name ASC");
 4
Author: Richard YS,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-07-30 10:29:40

Alternatywne rozwiązanie używa następującej klasy / interfejsu. Nie jest dynamiczny, ale działa.

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
Author: Mike Christiansen,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-03-16 02:05:29

Ta odpowiedź jest odpowiedzią na komentarze, które wymagają przykładowego rozwiązania dostarczonego przez @ John Sheehan-Runscope

Proszę podać przykład dla reszty z nas.

W DAL (Warstwa dostępu do danych),

Wersja IEnumerable:

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

Wersja IQueryable

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

Teraz możesz użyć wersji IQueryable do wiązania, na przykład GridView w Asp.net i korzyści dla sortowania (nie można sortować za pomocą IEnumerable version)

Użyłem Dapper jako ORM i zbudowałem IQueryable version i wykorzystałem sortowanie w GridView w asp.net to takie proste.

 1
Author: M.Hassan,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-11-23 20:34:58

Pierwsza Instalacja Dynamiczna Narzędzia -- > NuGet Package Manager -- > Package Manager Console

install-package System.Linq.Dynamic

Dodaj Przestrzeń Nazw using System.Linq.Dynamic;

Teraz możesz użyć OrderBy("Name, Age DESC")

 1
Author: Aminur Rahman,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-03-10 18:31:00

Konwertuj listę do IEnumerable lub Iquerable, dodaj za pomocą systemu.LINQ.Dynamiczna przestrzeń nazw, następnie można wymienić nazwy właściwości w oddzielonym przecinkiem łańcuchu do metody OrderBy, która domyślnie pochodzi z systemu.LINQ.Dynamiczny.

 0
Author: user145610,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-08-05 15:37:27
var result1 = lst.OrderBy(a=>a.Name);// for ascending order. 
 var result1 = lst.OrderByDescending(a=>a.Name);// for desc order. 
 -1
Author: Arindam,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-05-16 10:01:34