c# - Ordine LINQ dinamico su IEnumerable<T>




linq-to-objects (16)

Puoi convertire IEnumerable in IQueryable.

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

Ho trovato un esempio negli esempi VS2008 per LINQ dinamico che consente di utilizzare una stringa di tipo sql (ad esempio OrderBy("Name, Age DESC")) per l'ordine. Sfortunatamente, il metodo incluso funziona solo su IQueryable<T> ;. C'è un modo per ottenere questa funzionalità su IEnumerable<T> ?


Potresti 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 funzione GetPropertyValue proviene dalla risposta di Kjetil Watnedal

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

Se hai a che fare con Linq su Sql e l'orderby è un albero di espressioni, sarà comunque convertito in SQL per l'esecuzione.


var result1 = lst.OrderBy(a=>a.Name);// for ascending order. 
 var result1 = lst.OrderByDescending(a=>a.Name);// for desc order. 

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


Ho inciampato su questa domanda alla ricerca di più clausole di ordine di Linq e forse questo era ciò che l'autore stava cercando

Ecco come farlo:

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

Basandomi su ciò che altri hanno detto. Ho trovato 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;
}

Troppo facile senza alcuna complicazione:

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

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

Si prega di fornire un esempio per il resto di noi.

in DAL (Data Access Layer),

La versione IEnumerable:

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

La versione 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 IQueryable per legare, per esempio GridView in Asp.net e beneficiare dell'ordinamento (non puoi ordinare usando la versione IEnumerable)

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


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

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 in fase di esecuzione:

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

Il dinamico OrderBy 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 che OrderBy usasse IComparer personalizzato, perché volevo fare l' ordinamento naturale .

Per fare ciò, OrderBy solo l' OrderBy per:

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

Vedi questo post per il codice per NaturalSortComparer() .


Ecco qualcos'altro che ho trovato interessante. Se la fonte è un 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();

Per prima cosa installa gli strumenti dinamici -> NuGet Package Manager -> Console di gestione pacchetti

install-package System.Linq.Dynamic

Aggiungi spazio dei nomi using System.Linq.Dynamic;

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


Immagino che sarebbe utile usare la reflection per ottenere qualsiasi proprietà su cui vuoi 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'uso della reflection è notevolmente più lento dell'accesso diretto alla proprietà, quindi è necessario esaminare le prestazioni.


Sono appena entrato in questo vecchio ...

Per fare ciò senza la libreria LINQ dinamica, hai solo bisogno del codice come sotto. Questo copre gli scenari più comuni comprese le proprietà annidate.

Per farlo funzionare con IEnumerable<T> è possibile aggiungere alcuni metodi wrapper che vanno via AsQueryable - ma il codice sottostante è la logica di Expression 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 si vuole mescolarlo con la dynamic - sebbene si noti che la dynamic si applica solo a LINQ-to-Objects (gli alberi di espressione per ORM ecc non possono rappresentare realmente query dynamic - MemberExpression non la supporta). 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);
        }
    }
}

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


Secondo PLINQ (disponibile da .Net 4.0), puoi fare un

IEnumerable<T>.AsParallel().ForAll() 

fare un ciclo foreach parallelo su un oggetto IEnumerable.





c# linq linq-to-objects