Pregunta

Encontré un ejemplo en el Ejemplos de VS2008 para LINQ dinámico que le permite utilizar una cadena similar a SQL (p. ej. OrderBy("Name, Age DESC")) para realizar el pedido.Lamentablemente, el método incluido sólo funciona en IQueryable<T>;.¿Hay alguna forma de activar esta funcionalidad? IEnumerable<T>?

¿Fue útil?

Solución

Me acabo de topar con este viejo...

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

Para que funcione con IEnumerable<T> podrías agregar algunos métodos contenedores que pasen por AsQueryable - pero el código siguiente es el núcleo Expression Se necesita lógica.

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., realmente no pueden representar dynamic consultas - MemberExpression no lo soporta).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);
        }
    }
}

Otros consejos

Demasiado fácil sin ninguna complicación:

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

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 su contra.

Me encontré con esta pregunta.

Usando la implementación ApplyOrder de Marc desde arriba, creé 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

Supongo que funcionaría usar la reflexión para obtener cualquier propiedad que quieras 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 utilizar la reflexión es considerablemente más lento que acceder directamente a la propiedad, por lo que habría que investigar el rendimiento.

Simplemente basándose en 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;
}

He encontrado esta pregunta en busca de cláusulas de Linq Múltiple 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);    

Estaba intentando hacer esto pero tengo 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 fue intentar realizar una clasificación dinámica utilizando un método personalizado. 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 clasificación 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 usar un pequeño método auxiliar 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 usar personalizado IComparer - porque quería hacer clasificación natural.

Para hacer eso, simplemente altero el OrderBy a:

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

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

Podrías agregarlo:

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
}

El GetPropertyValue la función es de La respuesta de Kjetil Watnedal

La cuestión sería ¿por qué?Cualquier tipo de este tipo generaría excepciones en tiempo de ejecución, en lugar de tiempo de compilación (como la respuesta de D2VIANT).

Si está tratando con Linq to Sql y orderby es un árbol de expresión, se convertirá a SQL para su ejecución de todos modos.

Aquí hay algo más que encontré interesante.Si su fuente es un DataTable, puede usar la clasificación dinámica sin usar 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;

referencia: http://msdn.microsoft.com/en-us/library/bb669083.aspx (Usando extensiones de conjunto de datos)

Aquí hay una forma más de hacerlo convirtiéndolo a un DataView:

DataTable contacts = dataSet.Tables["Contact"];    
DataView view = contacts.AsDataView();    
view.Sort = "LastName desc, FirstName asc";    
bindingSource1.DataSource = view;
dataGridView1.AutoResizeColumns();

Gracias a Martín (Consultar una colección utilizando el objeto PropertyInfo en LINQ) Obtuve esta solución:

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

En mi caso, estaba trabajando en "ColumnHeaderMouseClick" (WindowsForm), así que encontré la columna específica presionada y su correspondiente 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();

(asegúrese de que los nombres de sus columnas coincidan con las propiedades del objeto)

Salud

Después de mucho buscar esto funcionó para mí:

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

Puede convertir IEnumerable a IQueryable.

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

Una solución alternativa utiliza la siguiente clase/interfaz.No es realmente dinámico, pero funciona.

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

Esta respuesta es una respuesta a los comentarios que necesitan un ejemplo para la solución proporcionada por @John Sheehan - Runscope

Por favor, proporcione un ejemplo para el resto de nosotros.

en DAL (Capa de acceso a datos),

La versión IEnumerable:

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

La versión IQueryable

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

Ahora puede usar la versión IQueryable para vincular, por ejemplo GridView en Asp.net y beneficiarse de la clasificación (no puede ordenar usando la versión IEnumerable)

Utilicé Dapper como ORM y construí la versión IQueryable y utilicé la clasificación en GridView en asp.net muy fácilmente.

Primera instalación dinámicaHerramientas --> Administrador de paquetes NuGet --> Consola del administrador de paquetes

install-package System.Linq.Dynamic

Agregar Espacio de nombres using System.Linq.Dynamic;

Ahora puedes usar OrderBy("Name, Age DESC")

Convierta la lista a IEnumerable o Iquerable, agregue usando el espacio de nombres System.LINQ.Dynamic, luego puede mencionar los nombres de las propiedades en una cadena separada por comas en el método OrderBy que viene de forma predeterminada desde System.LINQ.Dynamic.

Usar dinámica linq

solo agrega using System.Linq.Dynamic;

Y úsalo así para ordenar todas tus columnas:

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. 
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top