Pregunta Dynamic LINQ OrderBy en IEnumerable


Encontré un ejemplo en el Ejemplos de VS2008 para Dynamic LINQ que le permite usar una cadena similar a sql (p. OrderBy("Name, Age DESC")) para ordenar Lamentablemente, el método incluido solo funciona en IQueryable<T>; ¿Hay alguna manera de obtener esta funcionalidad en IEnumerable<T>?


615
2017-09-03 06:30


origen


Respuestas:


Acabo de tropezar con este viejo ...

Para hacer esto sin la biblioteca LINQ dinámica, solo necesita el código siguiente. Esto cubre los escenarios más comunes, incluidas las propiedades anidadas.

Para que funcione con IEnumerable<T> podrías agregar algunos métodos de envoltura que pasan por AsQueryable - pero el código a continuación es el núcleo Expression lógica necesaria.

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

Editar: se vuelve más divertido si quieres mezclar eso con dynamic - aunque tenga en cuenta que dynamic solo se aplica a LINQ-to-Objects (los árboles de expresión para ORM etc. no pueden representar dynamic consultas - MemberExpression no lo admite). Pero aquí hay una manera de hacerlo con LINQ-to-Objects. Tenga en cuenta que la elección de Hashtable se debe a una semántica de bloqueo 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



Demasiado fácil sin ninguna complicación:

  1. Añadir using System.Linq.Dynamic; en la cima.
  2. Utilizar vehicles = vehicles.AsQueryable().OrderBy("Make ASC, Year DESC").ToList();

206
2017-12-28 19:24



Encontré la respuesta. Puedo usar el .AsQueryable<>() método de extensión para convertir mi lista a IQueryable, luego ejecute el orden dinámico en contra de él.


77
2017-09-16 17:19



Acabo de tropezar con esta pregunta.

Usando la implementación ApplyOrder de Marc desde arriba, di una bofetada a un método de extensión que maneja cadenas similares a SQL como:

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

Detalles pueden ser encontrados aqui: http://aonnull.blogspot.com/2010/08/dynamic-sql-like-linq-orderby-extension.html


50
2017-08-18 01:55



Supongo que funcionaría usar la reflexión para obtener cualquier propiedad que desee ordenar:

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

Tenga en cuenta que el uso de la reflexión es considerablemente más lento que el acceso a la propiedad directamente, por lo que el rendimiento tendría que ser investigado.


39
2017-09-03 07:00



Solo construyendo sobre lo que otros han dicho. Descubrí que lo siguiente funciona bastante 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



He tropezado con esta pregunta buscando cláusulas de orden múltiple de Linq y tal vez esto era lo que el autor estaba buscando

He aquí cómo hacerlo:

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

11
2017-12-16 00:00



Estaba tratando de hacer esto pero teniendo problemas con La solución de Kjetil Watnedal porque no uso la sintaxis de linq en línea; prefiero la sintaxis de estilo de método. Mi problema específico era tratar de hacer una clasificación dinámica usando una costumbre IComparer.

Mi solución terminó así:

Dada una consulta IQueryable como esta:

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

Y dado un argumento de campo de ordenamiento en tiempo de ejecución:

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

El OrderBy dinámico se ve así:

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

Y eso es usando un pequeño método de ayuda llamado GetReflectedPropertyValue ():

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

Una última cosa: mencioné que quería el OrderBy para usar personalizado IComparer - porque yo quería hacer Clasificación natural.

Para hacer eso, solo cambio el OrderBy a:

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

Ver esta publicación para el código de NaturalSortComparer().


9
2017-10-29 11:01