Dynamic LINQ OrderBy en IEnumerable

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

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 , puede agregar algunos métodos de contenedor que pasan por AsQueryable , pero el código siguiente es la lógica de Expression necesaria.

 public static IOrderedQueryable OrderBy( this IQueryable source, string property) { return ApplyOrder(source, property, "OrderBy"); } public static IOrderedQueryable OrderByDescending( this IQueryable source, string property) { return ApplyOrder(source, property, "OrderByDescending"); } public static IOrderedQueryable ThenBy( this IOrderedQueryable source, string property) { return ApplyOrder(source, property, "ThenBy"); } public static IOrderedQueryable ThenByDescending( this IOrderedQueryable source, string property) { return ApplyOrder(source, property, "ThenByDescending"); } static IOrderedQueryable ApplyOrder( IQueryable 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)result; } 

Editar: se vuelve más divertido si quieres mezclar eso con dynamic , aunque ten en cuenta que la dynamic solo se aplica a LINQ-to-Objects (los árboles de expresión para ORM, etc., realmente no pueden representar consultas dynamic , 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 locking 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> GetCallSiteLocked( string name) { var callSite = (CallSite>)callSites[name]; if(callSite == null) { callSites[name] = callSite = CallSite> .Create(Binder.GetMember( CSharpBinderFlags.None, name, typeof(AccessorCache), new CSharpArgumentInfo[] { CSharpArgumentInfo.Create( CSharpArgumentInfoFlags.None, null) })); } return callSite; } internal static Func GetAccessor(string name) { Func accessor = (Func)accessors[name]; if (accessor == null) { lock (accessors ) { accessor = (Func)accessors[name]; if (accessor == null) { if(name.IndexOf('.') >= 0) { string[] props = name.Split('.'); CallSite>[] 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 OrderBy( this IEnumerable source, string property) { return Enumerable.OrderBy( source, AccessorCache.GetAccessor(property), Comparer.Default); } public static IOrderedEnumerable OrderByDescending( this IEnumerable source, string property) { return Enumerable.OrderByDescending( source, AccessorCache.GetAccessor(property), Comparer.Default); } public static IOrderedEnumerable ThenBy( this IOrderedEnumerable source, string property) { return Enumerable.ThenBy( source, AccessorCache.GetAccessor(property), Comparer.Default); } public static IOrderedEnumerable ThenByDescending( this IOrderedEnumerable source, string property) { return Enumerable.ThenByDescending( source, AccessorCache.GetAccessor(property), Comparer.Default); } static void Main() { dynamic a = new ExpandoObject(), b = new ExpandoObject(), c = new ExpandoObject(); aX = "abc"; bX = "ghi"; cX = "def"; dynamic[] data = new[] { new { Y = a }, new { Y = b }, new { Y = c } }; var ordered = data.OrderByDescending("YX").ToArray(); foreach (var obj in ordered) { Console.WriteLine(obj.YX); } } } 

Demasiado fácil sin ninguna complicación:

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

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

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

Los detalles se pueden encontrar aquí: 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 desee ordenar:

 IEnumerable 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.

Solo construyendo sobre lo que otros han dicho. Descubrí que lo siguiente funciona bastante bien.

 public static IEnumerable OrderBy(this IEnumerable 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)input).ThenBy(x => GetPropertyValue(x, subContent[0].Trim())); } else { if (i == 0) input = input.OrderByDescending(x => GetPropertyValue(x, subContent[0].Trim())); else input = ((IOrderedEnumerable)input).ThenByDescending(x => GetPropertyValue(x, subContent[0].Trim())); } i++; } return input; } 

He tropezado con esta pregunta en busca de cláusulas de ordenamiento 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); 

Intenté hacer esto pero tuve problemas con la solución de Kjetil Watnedal porque no uso la syntax de linq en línea, prefiero la syntax de estilo de 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 como esta:

 List 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 dynamic 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 que OrderBy usara IComparer personalizado porque quería hacer una ordenación natural .

Para hacer eso, solo cambio el OrderBy a:

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

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

Puedes agregarlo:

 public static IEnumerable OrderBy( this IEnumerable 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 la respuesta de Kjetil Watnedal

El problema sería por qué? Cualquier tipo de este tipo arrojaría excepciones en tiempo de ejecución, en lugar de tiempo de comstackción (como la respuesta de D2VIANT).

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

Aquí hay algo más que me pareció interesante. Si su fuente es una DataTable, puede usar la clasificación dinámica sin utilizar Dynamic Linq

 DataTable orders = dataSet.Tables["SalesOrderHeader"]; EnumerableRowCollection query = from order in orders.AsEnumerable() orderby order.Field("OrderDate") select order; DataView view = query.AsDataView(); bindingSource1.DataSource = view; 

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

Aquí hay una forma más de hacerlo al convertirlo en un DataView:

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

Gracias a Maarten ( 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 un “ColumnHeaderMouseClick” (WindowsForm), así que solo 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 tener sus Nombres de columna que coincidan con las Propiedades del objeto)

Aclamaciones

Después de muchas búsquedas, esto funcionó para mí:

 public static IEnumerable OrderBy(this IEnumerable 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(resultExpression); } 

Puede convertir IEnumerable en IQueryable.

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

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

 public interface IID { int ID { get; set; } } public static class Utils { public static int GetID(ObjectQuery 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 provista por @John Sheehan – Runscope

Por favor, den un ejemplo para el rest de nosotros.

en DAL (Capa de acceso a datos),

La versión IEnumerable:

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

La versión IQueryable

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

Ahora puede usar la versión de IQueryable para enlazar, 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 compilé la versión IQueryable y utilicé la clasificación en GridView en asp.net de forma muy sencilla.

Convierta List en IEnumerable o Iquerable, agregue usando System.LINQ.Dynamic namespace, luego puede mencionar los nombres de las propiedades en coma separada por comas en OrderBy Method, que viene de forma predeterminada de System.LINQ.Dynamic.

Primero instale las herramientas dinámicas -> NuGet Package Manager -> Package Manager Console

 install-package System.Linq.Dynamic 

Agregar espacio de nombres using System.Linq.Dynamic;

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

 var result1 = lst.OrderBy(a=>a.Name);// for ascending order. var result1 = lst.OrderByDescending(a=>a.Name);// for desc order.