c# - Ordine LINQ dinamico su IEnumerable<T>




linq-to-objects (12)

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> ?


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

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 virgole al metodo OrderBy, che viene fornito per impostazione predefinita da System.LINQ.Dynamic.


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

Grazie a Maarten ( Query 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 a "ColumnHeaderMouseClick" (WindowsForm), quindi ho appena trovato la colonna specifica premuto e il suo PropertyInfo corrispondente:

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 di avere i nomi delle colonne corrispondenti alle proprietà dell'oggetto)

Saluti


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.


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.


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


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.


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.


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

Troppo facile senza alcuna complicazione:

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

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




linq-to-objects