Ordine LINQ dinamico Da IEnumerable <T> / IQueryable <T>


670

Ho trovato un esempio negli esempi VS2008 per Dynamic LINQ che consente di utilizzare una stringa di tipo sql (ad es OrderBy("Name, Age DESC")). Per l'ordinazione. Sfortunatamente, il metodo incluso funziona solo IQueryable<T>. Esiste un modo per attivare questa funzionalità IEnumerable<T>?


1
La migliore risposta a partire da questa data, a mio avviso: libreria System.Linq.Dynamic.Core .
Shahin Dohan,

Risposte:


905

Sono appena inciampato in questo vecchio ...

Per fare ciò senza la libreria LINQ dinamica, hai solo bisogno del codice come di seguito. Questo copre gli scenari più comuni tra cui le proprietà nidificate.

Per farlo funzionare IEnumerable<T>è possibile aggiungere alcuni metodi wrapper che passano attraverso AsQueryable, ma il codice seguente è la Expressionlogica di base necessaria.

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

Modifica: diventa più divertente se vuoi mescolarlo con dynamic- anche se nota che dynamicsi applica solo a LINQ-to-Objects (gli alberi delle espressioni per ORM ecc. Non possono davvero rappresentare le dynamicquery - MemberExpressionnon lo supportano). Ma ecco un modo per farlo con LINQ-to-Objects. Si noti che la scelta di Hashtableè dovuta alla semantica di blocco favorevole:

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
Il miglior dannato pezzo di codice che ho visto :) Ho appena risolto un milione di problemi nel mio progetto :)
sajidnizami,

4
@Dave - devi iniziare IQueryable<T>, quindi se hai qualcosa di simile List<T>(che è IEnumerable<T>) potresti dover usare AsQueryable()- ad esempiovar sorted = someList.AsQueryable().OrderBy("Foo.Bar");
Marc Gravell

7
Hai visto questo ... potrebbe aiutare alcune persone ... stackoverflow.com/questions/557819/… è una soluzione più fortemente tipizzata.
anthonyv,

28
@MGOwen sembra che tu abbia frainteso la natura del codice. Le 40 linee sono le stesse, indipendentemente dal fatto che siano 40 linee che metti da qualche parte nel tuo progetto, o se quelle linee arrivano (pre-compilate o come sorgente) in una libreria esterna. Sarebbe stato piuttosto sorprendente se mi fossi collegato, nell'ottobre '08 a una biblioteca su nuget che esiste dal dicembre '11 (non ultimo perché non esisteva nemmeno il nuget), ma il "cosa sta facendo" fondamentale è lo stesso. Inoltre, usi la frase "soluzione effettiva" come se ci fosse un percorso unico concordato ben definito per ogni domanda di codifica: non esiste.
Marc Gravell

5
@MGOwen tra l'altro, la lib esterna è di 2296 righe di codice (escluso AssemblyInfo.cs); che rende le 40 linee qui abbastanza ragionevoli
Marc Gravell

231

Troppo facile senza alcuna complicazione:

  1. Aggiungi using System.Linq.Dynamic;in alto.
  2. Uso vehicles = vehicles.AsQueryable().OrderBy("Make ASC, Year DESC").ToList();

11
e da dove l'hai preso System.Linq.Dynamic?
Demenza

1
Funziona anche con linq con MongoDB.
soupy1976,

32
La risposta accettata potrebbe essere stata la risposta corretta nel 2008, ma attualmente questa è la risposta più semplice e corretta ora.
EL MOJO,

1
Questa è davvero una buona e semplice gestione, tanta complessità interna, adorabile
Mrinal Kamboj,

5
Per le persone nel "futuro", se si utilizza dotnet core, utilizzare questo: nuget.org/packages/System.Linq.Dynamic.Core
Rafael Merlin,

78

Ho trovato la risposta Posso usare il .AsQueryable<>()metodo di estensione per convertire il mio elenco in IQueryable, quindi eseguire l'ordine dinamico contro di esso.


52
Fornisci un esempio per il resto di noi.
MGOwen,

54

Mi sono appena imbattuto in questa domanda.

Utilizzando l'implementazione ApplyOrder di Marc dall'alto, ho messo insieme un metodo di estensione che gestisce stringhe simili a SQL come:

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

I dettagli sono disponibili qui: http://aonnull.blogspot.com/2010/08/dynamic-sql-like-linq-orderby-extension.html


1
Ottime cose, basta aggiungere una modifica come segue per rendere insensibile il nome della proprietà: PropertyInfo pi = type.GetProperty (prop, BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase);
Mrinal Kamboj,

43

Immagino che funzionerebbe con la riflessione per ottenere qualsiasi proprietà su cui desideri ordinare:

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

Si noti che l'utilizzo della reflection è notevolmente più lento dell'accesso diretto alla proprietà, pertanto è necessario esaminare le prestazioni.


funziona anche questo? orderby non vuole un valore ma un selettore lamba / delegato (Func <TSource, TKey> keySelector) ..
Davy Landman

2
Ho provato questo esempio prima di pubblicarlo, e sì, funziona.
Kjetil Watnedal,

3
+1 Questo è esattamente quello che stavo cercando! Funzionerà perfettamente per semplici problemi di ordinamento delle pagine.
Andrew Siemer,

Questo non ha funzionato per me. Mi sto perdendo qualcosa? Cosa dovrebbe essere "SomeProperty". Ho provato a dare il nome della proprietà e property.GetType (). Ho IQueryable <> e non IEnumerable <>
SO User

2
@Alex Shkor: Come dovresti ordinare gli elementi senza guardare tutti gli elementi? Tuttavia, ci sono soluzioni migliori in altre risposte.
Kjetil Watnedal,

19

Basandosi su ciò che altri hanno detto. Ho scoperto che il seguente funziona abbastanza bene.

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

Ho inciampato in questa domanda cercando Linq multiple orderby clausole e forse questo era quello che l'autore stava cercando

Ecco come farlo:

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

5
+1 ha annullato il voto negativo a causa della mancanza di spiegazioni. Penso anche che l'autore avrebbe potuto essere interessato a più ordini. Anche se dinamica era la parola chiave, nessun motivo per votare in negativo.
Jason Kleban,

11

Stavo cercando di farlo, ma ho avuto problemi con la soluzione di Kjetil Watnedal perché non uso la sintassi linq in linea - preferisco la sintassi in stile metodo. Il mio problema specifico era provare a fare l'ordinamento dinamico usando un'abitudine IComparer.

La mia soluzione è finita così:

Data una query IQueryable in questo modo:

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

E dato un argomento del campo di ordinamento runtime:

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

OrderBy dinamico si presenta così:

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

E questo sta usando un piccolo metodo di supporto chiamato GetReflectedPropertyValue ():

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

Un'ultima cosa - ho detto che volevo l' OrderByuso personalizzato IComparer- perché volevo fare l' ordinamento naturale .

Per fare ciò, ho appena modificato il OrderBy:

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

Vedi questo post per il codice per NaturalSortComparer().


5

Usa dinamica linq

basta aggiungere using System.Linq.Dynamic;

E usalo in questo modo per ordinare tutte le tue colonne:

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

4

Puoi aggiungerlo:

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
}

La GetPropertyValuefunzione è dalla risposta di Kjetil Watnedal

Il problema sarebbe perché? Qualsiasi tipo di questo genererebbe eccezioni in fase di esecuzione, anziché in fase di compilazione (come la risposta di D2VIANT).

Se hai a che fare con Linq in SQL e l'ordine è un albero delle espressioni, verrà comunque convertito in SQL per l'esecuzione.


MeProod GetPropertyValue verrà eseguito per tutti gli elementi, è una cattiva soluzione.
Alex Shkor,

2
OrderBynon mantenere l'ordine precedente !!
Amir Ismail il

4

Ecco qualcos'altro che ho trovato interessante. Se l'origine è una DataTable, è possibile utilizzare l'ordinamento dinamico senza utilizzare 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;

riferimento: http://msdn.microsoft.com/en-us/library/bb669083.aspx (Utilizzo di DataSetExtensions)

Ecco un altro modo per farlo convertendolo in un DataView:

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

4

Grazie a Maarten ( interroga una raccolta usando l'oggetto PropertyInfo in LINQ ) ho ottenuto questa soluzione:

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

Nel mio caso stavo lavorando su un "ColumnHeaderMouseClick" (WindowsForm) quindi ho appena trovato premuto lo specifico Column e il corrispondente PropertyInfo:

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

O

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

(assicurati che la colonna Nomi corrisponda all'oggetto Proprietà)

Saluti


4

Dopo molte ricerche questo ha funzionato per 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);
}

4

È possibile convertire IEnumerable in IQueryable.

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

3

Una soluzione alternativa utilizza la seguente classe / interfaccia. Non è veramente dinamico, ma funziona.

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

Questa risposta è una risposta ai commenti che richiedono un esempio per la soluzione fornita da @John Sheehan - Runscope

Fornisci un esempio per il resto di noi.

in DAL (livello di accesso ai dati),

La versione di IEnumerable:

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

La versione di IQueryable

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

Ora puoi usare la versione di IQueryable per eseguire il bind, ad esempio GridView in Asp.net e beneficiare dell'ordinamento (non puoi ordinare utilizzando la versione di IEnumerable)

Ho usato Dapper come ORM e ho creato la versione di IQueryable e ho usato l'ordinamento in GridView in asp.net così facilmente.


2

Installare prima Dynamic Tools -> NuGet Package Manager -> Package Manager Console

install-package System.Linq.Dynamic

Aggiungi spazio dei nomi using System.Linq.Dynamic;

Adesso puoi usare OrderBy("Name, Age DESC")


Come posso usarlo con l'ordinamento interno delle proprietà - come OrderBy ("Branch.BranchName", "Descending")
devC

Questo funziona per me. Forse perché la domanda ha 10 anni e questo metodo più semplice è arrivato solo dopo.
kosherjellyfish,

1

Puoi usare questo:

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

Un paio d'anni dopo e mi imbatto in questo; questo ha funzionato per me, come un sogno. Ho un ordinamento dinamico da 1 a 3 proprietà e questo funziona come un sogno. Facile da implementare e senza problemi.
Bazinga il

0

Converti Elenco in IEnumerable o Iquerable, aggiungi utilizzando lo spazio dei nomi System.LINQ.Dynamic, quindi puoi menzionare i nomi delle proprietà nella stringa separata da virgola in Metodo OrderBy che proviene per impostazione predefinita da System.LINQ.Dynamic.


-3
var result1 = lst.OrderBy(a=>a.Name);// for ascending order. 
 var result1 = lst.OrderByDescending(a=>a.Name);// for desc order. 
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.