2008-09-03 9 views

Respuesta

824

tropezado este oldie ...

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

Para que funcione con IEnumerable<T> puede agregar algunos métodos de contenedor que van a través de AsQueryable - pero el siguiente código es el núcleo Expression lógica necesaria.


Editar: se vuelve más divertido si usted quiere mezclar eso con dynamic - aunque nota que dynamic sólo se aplica a LINQ a Objetos (expresión-árboles de ORM etc realmente 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 la semántica de bloqueo favorables:

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

Mejor pedazo de código que he visto :) Acabo de resolver un millón de problemas en mi proyecto :) – sajidnizami

+0

Marc, ¿Tiene la extensión similar para la condición LIKE? – Prasad

+0

@Prasad - 'LIKE' es un poco diferente, pero ¿qué back-end? Para LINQ-to-SQL, hay 'SqlMethods.Like': http://msdn.microsoft.com/en-us/library/system.data.linq.sqlclient.sqlmethods.like.aspx; ¿Puedes agregar más información de lo que estás buscando? –

39

supongo que funcionaría a utilizar la reflexión para conseguir cualquier propiedad que desea ordenar en:?

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 acceder directamente al establecimiento, por lo el rendimiento woul d tiene que ser investigado.

+0

hace esto, incluso ¿trabajo?orderby no quiere un valor sino un selector lamba/delegate (Func keySelector) .. –

+1

Intenté este ejemplo antes de publicarlo, y sí, funciona. –

+0

Esta solución funciona bien para la clasificación simple de propiedades individuales. –

4

Se podría añadir que:

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 función GetPropertyValue es de Kjetil Watnedal's answer

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

Si se trata de Linq a Sql y el orden es un árbol de expresiones, se convertirá en SQL para su ejecución de todos modos.

+0

GetPropertyValue Mehotod se ejecutará para todos los elementos, es una mala solución. –

+2

'OrderBy' no mantiene el orden anterior !! –

76

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

+43

Proporcione un ejemplo para el resto de nosotros. – MGOwen

11

he tropiezo esta pregunta en busca de Linq cláusulas múltiples OrdenarPor y tal vez esto era lo que el autor buscaba

Aquí es cómo hacer eso:

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

+1 canceló el voto atrasado debido a la falta de explicación. También creo que el autor podría haber estado interesado en varios pedidos por bys. Incluso si dynamic * was * la palabra clave, no hay razón para votar abajo. –

2

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

Basándome 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; 
} 
9

yo estaba tratando de hacer esto, pero tienen problemas con Kjetil Watnedal's solution porque yo no uso la sintaxis de LINQ en línea - Yo prefiero la sintaxis de estilo método. Mi problema específico era tratar de hacer una clasificación dinámica usando un IComparer personalizado.

Mi solución terminó así:

Dada una consulta IQueryable así:

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

Y dado un tiempo de ejecución argumento campo de clasificación:

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

Las dinámicas miradas OrdenarPor como ese:

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

Y eso con un poco de método de ayuda llamada 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 - he mencionado que quería que el OrderBy de usar encargo IComparer - porque quería hacer Natural sorting.

Para hacer eso, sólo altera el OrderBy a:

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

Ver this post para el código de NaturalSortComparer().

+0

Recibí este error: LINQ to Entities no reconoce el método 'String GetReflectedPropertyValue (Object, String)', y este método no se puede traducir a una expresión de tienda. – Misi

4

aquí hay algo más que me pareció interesante. Si la fuente es un DataTable, puede utilizar la clasificación dinámica sin usar dinámico 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; 

de referencia: http://msdn.microsoft.com/en-us/library/bb669083.aspx (usando DataSetExtensions)

Aquí es una forma más de hacerlo mediante su conversión a un DataView:

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

Acabo de tropezar con esta pregunta.

Usando aplicación ApplyOrder de Marc desde arriba, me dio una palmada en conjunto un método de extensión que se encarga de cadenas SQL-como como:

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

detalles se pueden encontrar aquí: http://aonnull.blogspot.com/2010/08/dynamic-sql-like-linq-orderby-extension.html

+0

Cosas grandiosas, solo agregue una modificación de la siguiente manera para que el nombre de la propiedad no distinga entre mayúsculas y minúsculas: PropertyInfo pi = type.GetProperty (prop, BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase); –

189

Demasiado fácil y sin ningún tipo de complicación:

  1. Agregue using System.Linq.Dynamic; en la parte superior.
  2. Use vehicles = vehicles.AsQueryable().OrderBy("Make ASC, Year DESC").ToList();
+10

y de dónde obtuviste el 'System.Linq.Dynamic'? – Dementic

+24

https://nuget.org/packages/System.Linq.Dynamic – tomfanning

+1

Funciona al usar linq con MongoDB también. – soupy1976

4

Gracias a Maarten (Query a collection using PropertyInfo object in LINQ) Tengo esta solución:

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

En mi caso, yo estaba trabajando en un "ColumnHeaderMouseClick" (WindowsForm) por lo que sólo se encontró la columna específica presionado y su PropertyInfo corresponsal:

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 tener sus nombres de las columnas que coinciden con las propiedades de los objetos)

Saludos

4

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

lista Convertir a IEnumerable o Iquerable , agregue el uso del espacio de nombres System.LINQ.Dynamic, luego puede mencionar los nombres de las propiedades en una secuencia separada por comas en el Método OrderBy que viene de forma predeterminada de System.LINQ.Dynamic.

4

Puede convertir IEnumerable en IQueryable.

items = items.AsQueryable().OrderBy("Name ASC"); 
-2
var result1 = lst.OrderBy(a=>a.Name);// for ascending order. 
var result1 = lst.OrderByDescending(a=>a.Name);// for desc order. 
0

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

Sírvanse proporcionar un ejemplo para el resto de nosotros.

en DAL (Data Access Layer),

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 utilizar la versión IQueryable para enlazar, por ejemplo, Grid Ver en Asp.net y beneficiarse de la clasificación (no se puede ordenar usando la versión IEnumerable)

Utilicé Dapper como ORM y compilé la versión IQueryable y utilicé la clasificación en GridView en asp.net de manera fácil.

0

primera instalación dinámicos Herramientas -> Gestor de paquetes NuGet -> Gestor de paquetes de consola

install-package System.Linq.Dynamic 

Añadir Espacio de nombresusing System.Linq.Dynamic;

Ahora puede utilizar OrderBy("Name, Age DESC")

Cuestiones relacionadas