Domanda

Ho trovato un esempio in Esempi di VS2008 per Dynamic LINQ che ti consente di utilizzare una stringa simile a SQL (ad es. OrderBy("Name, Age DESC")) per ordinare.Sfortunatamente, il metodo incluso funziona solo su IQueryable<T>;.Esiste un modo per attivare questa funzionalità IEnumerable<T>?

È stato utile?

Soluzione

Mi sono appena imbattuto in questo vecchio pezzo...

Per fare ciò senza la libreria dinamica LINQ, è sufficiente il codice come di seguito.Questo copre gli scenari più comuni, comprese le proprietà nidificate.

Per farlo funzionare IEnumerable<T> potresti aggiungere alcuni metodi wrapper che passano AsQueryable - ma il codice seguente è il nucleo Expression logica 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;
}

Modificare:diventa più divertente se vuoi mescolarlo dynamic - anche se tienilo presente dynamic si applica solo a LINQ-to-Objects (gli alberi delle espressioni per ORM ecc. non possono realmente rappresentare dynamic interrogazioni - MemberExpression non lo supporta).Ma ecco un modo per farlo con LINQ-to-Objects.Si noti che la scelta di Hashtable è dovuto 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);
        }
    }
}

Altri suggerimenti

Troppo facile senza alcuna complicazione:

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

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

Mi sono appena imbattuto in questa domanda.

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

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

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

Immagino che funzionerebbe usare la riflessione per ottenere qualunque proprietà tu voglia 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);
}

Tieni presente che l'utilizzo della riflessione è notevolmente più lento rispetto all'accesso diretto alla proprietà, quindi le prestazioni dovrebbero essere analizzate.

Basta basarsi su ciò che hanno detto gli altri.Ho scoperto che quanto segue 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;
}

Ho inciampato questa domanda alla ricerca di clausole più ordinarie Linq e forse questo era quello che l'autore stava cercando

Ecco come farlo:

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

Stavo provando a farlo ma ho avuto problemi con La soluzione di Kjetil Watnedal perché non utilizzo la sintassi linq inline: preferisco la sintassi in stile metodo.Il mio problema specifico era provare a eseguire l'ordinamento dinamico utilizzando un file custom 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 in fase di esecuzione:

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

L'OrderBy dinamico si presenta così:

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

E questo utilizza 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 il OrderBy utilizzare personalizzato IComparer - perché volevo farlo Ordinamento naturale.

Per fare ciò, modifico semplicemente il file OrderBy A:

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

Vedere questo post per il codice per NaturalSortComparer().

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
}

IL GetPropertyValue la funzione proviene da La risposta di Kjetil Watnedal

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

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

Ecco qualcos'altro che ho trovato interessante.Se l'origine è un DataTable, puoi 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 (Utilizzando le estensioni DataSet)

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 (Eseguire una query su una raccolta utilizzando 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 la colonna specifica premuta e il suo 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 i nomi delle colonne corrispondano alle proprietà dell'oggetto)

Saluti

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

È possibile convertire IEnumerable in IQueryable.

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

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

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

Per favore, fornisci un esempio per il resto di noi.

in DAL (livello di accesso ai dati),

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

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

Prima installa DynamicStrumenti --> Gestione pacchetti NuGet --> Console di gestione pacchetti

install-package System.Linq.Dynamic

Aggiungere Spazio dei nomi using System.Linq.Dynamic;

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

Converti List in IEnumerable o Iquerable, aggiungi utilizzando lo spazio dei nomi System.LINQ.Dynamic, quindi puoi menzionare i nomi delle proprietà in una stringa separata da virgole al metodo OrderBy che proviene per impostazione predefinita da System.LINQ.Dynamic.

Usa la 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}");
var result1 = lst.OrderBy(a=>a.Name);// for ascending order. 
 var result1 = lst.OrderByDescending(a=>a.Name);// for desc order. 
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top