Question Ordre LINQ dynamiquePar IEnumerable


J'ai trouvé un exemple dans le VS2008 Exemples pour Dynamic LINQ qui vous permet d'utiliser une chaîne de type sql (par ex. OrderBy("Name, Age DESC")) pour commander. Malheureusement, la méthode incluse ne fonctionne que sur IQueryable<T>. Est-il possible d'obtenir cette fonctionnalité sur IEnumerable<T>?


615
2017-09-03 06:30


origine


Réponses:


Juste trébuché dans cette oldie ...

Pour ce faire, sans la bibliothèque LINQ dynamique, vous avez juste besoin du code 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 des méthodes d'emballage 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: ça devient plus amusant si vous voulez mélanger ça avec dynamic - bien noter que dynamic s'applique uniquement à LINQ-to-Objects (les arborescences d'expression pour ORM, etc. ne peuvent pas vraiment représenter dynamic requêtes - 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);
        }
    }
}

843
2017-10-24 13:21



Trop facile sans aucune complication:

  1. Ajouter using System.Linq.Dynamic; au sommet.
  2. Utilisation vehicles = vehicles.AsQueryable().OrderBy("Make ASC, Year DESC").ToList();

206
2017-12-28 19:24



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


77
2017-09-16 17:19



Juste trébuché à travers cette question.

En utilisant l'application ApplyOrder de Marc ci-dessus, j'ai assemblé une méthode Extension qui gère les chaînes de type SQL comme:

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

Les détails peuvent être trouvés ici: http://aonnull.blogspot.com/2010/08/dynamic-sql-like-linq-orderby-extension.html


50
2017-08-18 01:55



Je suppose que cela fonctionnerait pour utiliser la réflexion pour obtenir la propriété que vous voulez trier:

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 considérablement plus lente que l'accès direct à la propriété, donc la performance devrait être étudiée.


39
2017-09-03 07:00



Juste en s'appuyant sur ce que d'autres ont dit. J'ai trouvé que ce qui suit fonctionne plutôt 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
2018-04-01 07:04



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

Voici comment faire:

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

11
2017-12-16 00:00



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

Ma solution a fini comme ça:

Étant donné une requête IQueryable comme ceci:

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

Et étant donné un argument de champ de tri d'exécution:

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

Le OrderBy dynamique ressemble à ceci:

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

Et cela utilise une petite méthode d'assistance 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 OrderBy utiliser la coutume IComparer - parce que je voulais faire Tri naturel.

Pour ce faire, je viens de modifier le OrderBy à:

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

Voir ce post pour le code pour NaturalSortComparer().


9
2017-10-29 11:01



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
}

le GetPropertyValue la fonction est de La réponse de Kjetil Watnedal

La question serait pourquoi? Un tel tri produirait 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 to Sql et que orderby est un arbre d'expression, il sera quand même converti en SQL pour l'exécution.


4
2017-09-03 10:37