Dynamic LINQ OrderBy on IEnumerable

j'ai trouvé un exemple dans les VS2008 exemples pour LINQ dynamique qui vous permet d'utiliser une chaîne de type sql (par exemple OrderBy("Name, Age DESC")) pour la commande. Malheureusement, la méthode ne comprenait que des travaux sur IQueryable<T> ;. Est-il possible d'obtenir cette fonctionnalité sur IEnumerable<T> ?

624
demandé sur Robert Harvey 2008-09-03 10:30:31

18 réponses

vient de tomber sur cette vieille...

pour faire cela sans la Bibliothèque dynamique LINQ, vous avez juste besoin du code comme ci-dessous. Cela couvre les scénarios les plus courants, y compris les propriétés imbriquées.

pour le faire fonctionner avec IEnumerable<T> vous pouvez ajouter quelques méthodes d'enrubannage qui vont via AsQueryable - mais le code ci-dessous est le noyau Expression logique nécessaire.

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: il devient de plus en plus plaisir, si vous voulez mélanger avec dynamic - à noter toutefois que la dynamic s'applique uniquement à LINQ-to-Objets (expression-des arbres pour l'Orm, etc ne peut pas vraiment représenter dynamic interroge - MemberExpression ne le supporte pas). Mais voici un moyen de le faire avec LINQ-to-Objects. Notez que le choix de Hashtable est dû à une sémantique de verrouillage favorable:

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
répondu Marc Gravell 2017-12-11 08:52:02

trop facile sans aucune complication:

  1. ajouter using System.Linq.Dynamic; en haut.
  2. Utiliser vehicles = vehicles.AsQueryable().OrderBy("Make ASC, Year DESC").ToList();
208
répondu Alaa Osta 2015-12-16 09:35:50

j'ai trouvé la réponse. Je peux utiliser la méthode d'extension .AsQueryable<>() pour convertir ma liste en IQueryable, puis exécuter l'ordre dynamique par contre.

77
répondu John Sheehan 2011-08-30 02:25:23

vient de tomber sur cette question.

en utilisant L'implémentation ApplyOrder de Marc d'en haut, j'ai assemblé une méthode D'Extension qui gère les chaînes de type SQL comme:

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

détails ici: http://aonnull.blogspot.com/2010/08/dynamic-sql-like-linq-orderby-extension.html

50
répondu Adam Anderson 2010-08-18 01:55:30

je suppose qu'il serait utile d'utiliser la réflexion pour obtenir n'importe quelle propriété que vous voulez trier sur:

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

notez que l'utilisation de la réflexion est beaucoup plus lente que l'accès direct à la propriété, de sorte que la performance devrait être étudiée.

39
répondu Kjetil Watnedal 2008-10-28 07:21:49

Juste en appuyant sur ce que les autres ont dit. J'ai trouvé que ce qui suit fonctionne assez bien.

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
répondu vdhant 2015-12-16 09:36:22

j'ai trébuché cette question à la recherche de Linq clauses orderby multiples et peut-être que c'est ce que l'auteur cherchait

Voici comment faire:

var query = pets.OrderBy(pet => pet.Name).ThenByDescending(pet => pet.Age);    
11
répondu InfoStatus 2015-04-27 21:48:37

j'essayais de le faire mais j'ai des problèmes avec solution de Kjetil Watnedal parce que je n'utilise pas la syntaxe linq inline - je préfère la syntaxe de style de méthode. Mon problème spécifique était d'essayer de faire du tri dynamique en utilisant un IComparer personnalisé .

ma solution a fini comme ceci:

donne une requête IQueryable comme ceci:

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

Et donné un champ de tri argument:

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

La dynamique OrderBy ressemble à ceci:

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

et c'est en utilisant une petite méthode d'aide appelée GetReflectedPropertyValue ():

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

une dernière chose - j'ai mentionné que je voulais que le OrderBy pour utiliser la coutume IComparer - parce que je voulais faire tri naturel .

OrderBy à:

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

Voir ce post le code NaturalSortComparer() .

9
répondu James McCormack 2017-05-23 11:47:27

vous pouvez l'ajouter:

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 fonction GetPropertyValue est de réponse de Kjetil Watnedal

La question serait pourquoi? Une telle sorte lancerait des exceptions au moment de l'exécution, plutôt que de compiler le temps (comme la réponse de D2VIANT).

si vous avez affaire à Linq en Sql et que l'orderby est un arbre d'expression, il sera converti en SQL pour exécution de toute façon.

4
répondu Keith 2017-05-23 12:34:37

voilà autre chose que j'ai trouvé intéressant. Si votre source est une DataTable, vous pouvez utiliser le tri dynamique sans utiliser la ligne dynamique 151930920"

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;

référence: http://msdn.microsoft.com/en-us/library/bb669083.aspx (utilisant les extensions des données)

Voici une autre façon de le faire en le convertissant en une DataView:

DataTable contacts = dataSet.Tables["Contact"];    
DataView view = contacts.AsDataView();    
view.Sort = "LastName desc, FirstName asc";    
bindingSource1.DataSource = view;
dataGridView1.AutoResizeColumns();
4
répondu Sameer 2010-01-13 20:01:38

merci à Maarten ( interrogation d'une collection en utilisant L'objet PropertyInfo dans LINQ ) j'ai obtenu cette solution:

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

dans mon cas je travaillais sur un "ColumnHeaderMouseClick" (WindowsForm) donc je viens de trouver la colonne spécifique pressée et son correspondant PropertyInfo:

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

ou

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

(assurez-vous que les noms de vos colonnes correspondent aux propriétés de l'objet)

Cheers

4
répondu joaopintocruz 2017-05-23 11:47:27

après beaucoup de recherches cela a fonctionné pour moi:

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
répondu Sanchitos 2013-10-24 13:29:15

vous pouvez convertir le IEnumerable en IQueryable.

items = items.AsQueryable().OrderBy("Name ASC");
4
répondu Richard YS 2014-07-30 10:29:40

une solution alternative utilise la classe/interface suivante. Ce n'est pas vraiment dynamique, mais il fonctionne.

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
répondu Mike Christiansen 2009-03-16 02:05:29

Cette réponse est une réponse aux commentaires qui ont besoin d'un exemple pour la solution fournie par @John Sheehan - Runscope

, Veuillez fournir un exemple pour le reste d'entre nous.

in DAL (Data Access Layer),

L'interface IEnumerable version:

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

Le IQueryable version

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

Maintenant vous pouvez utiliser la version IQueryable pour lier, par exemple GridView dans Asp.net et l'avantage pour le tri (vous ne pouvez pas trier en utilisant la version IEnumerable)

j'ai utilisé Dapper comme ORM et construire la version IQueryable et utilisé le tri dans GridView en asp.net si facile.

1
répondu M.Hassan 2017-11-23 20:34:58

Première Installation Dynamique Outils --> Gestionnaire De Package NuGet --> Gestionnaire De Paquets De La Console

install-package System.Linq.Dynamic

Ajouter Espace De Noms using System.Linq.Dynamic;

Maintenant vous pouvez utiliser OrderBy("Name, Age DESC")

1
répondu Aminur Rahman 2018-03-10 18:31:00

convertir la liste en IEnumerable ou Iquerable, ajouter en utilisant le système.LINQ.Dynamique de l'espace de noms, alors u peut citer les noms de propriété de séparation par virgule chaîne de OrderBy Méthode qui vient par défaut du Système.LINQ.Dynamique.

0
répondu user145610 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
répondu Arindam 2016-05-16 10:01:34