scottgu - dynamic linq c#
Orden LINY dinĂ¡mico de LINQ en IEnumerable<T> (18)
Aquí hay algo más que me pareció 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 (utilizando DataSetExtensions)
Aquí hay una forma más de hacerlo convirtiéndolo en un DataView:
DataTable contacts = dataSet.Tables["Contact"];
DataView view = contacts.AsDataView();
view.Sort = "LastName desc, FirstName asc";
bindingSource1.DataSource = view;
dataGridView1.AutoResizeColumns();
Encontré un ejemplo en los ejemplos de VS2008 para Dynamic LINQ que le permite usar una cadena tipo sql (por ejemplo, OrderBy("Name, Age DESC"))
para ordenar. Desafortunadamente, el método incluido solo funciona en IQueryable<T>
; ¿Hay alguna forma de obtener esta funcionalidad en IEnumerable<T>
?
Convierta la lista a IEnumerable o Iquerable, agregue usando el espacio de nombres System.LINQ.Dynamic, entonces u puede mencionar los nombres de las propiedades en una cadena separada por comas al Método OrderBy, que viene por defecto de System.LINQ.Dynamic.
Demasiado fácil sin ninguna complicación:
- Añadir
using System.Linq.Dynamic;
en la cima. - Use
vehicles = vehicles.AsQueryable().OrderBy("Make ASC, Year DESC").ToList();
Después de mucho buscar esto me funcionó:
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);
}
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.
Esta respuesta es una respuesta a los comentarios que necesitan un ejemplo de la solución provista por @John Sheehan - Runscope
Por favor, proporcione un ejemplo para el resto de nosotros.
en DAL (capa de acceso a datos),
La versión de 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 enlazar, por ejemplo, GridView en Asp.net y beneficios para la clasificación (no puede ordenar usando la versión IEnumerable)
Usé Dapper como ORM y construí la versión de IQueryable y utilicé la clasificación en GridView en asp.net de manera muy fácil.
Estaba tratando de 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 tratar de hacer una clasificación dinámica utilizando un IComparer
personalizado.
Mi solución terminó así:
Dada una consulta de IQueryable como tal:
List<DATA__Security__Team> teams = TeamManager.GetTeams();
var query = teams.Where(team => team.ID < 10).AsQueryable();
Y dado un argumento de campo de ordenación en tiempo de ejecución:
string SortField; // Set at run-time to "Name"
La dinámica de OrderBy 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 que OrderBy
usara IComparer
personalizado, porque quería hacer una clasificación natural .
Para hacer eso, solo cambio el OrderBy
a:
query = query.OrderBy(item => item.GetReflectedPropertyValue(SortField), new NaturalSortComparer<string>());
Vea esta publicación para el código de NaturalSortComparer()
.
Gracias a Maarten ( Consultar una colección usando 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 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 sus nombres de columna coincidan con las propiedades del objeto)
Aclamaciones
He tropezado con esta pregunta en busca de Linq de múltiples órdenes byby cláusulas y tal vez esto era lo que el autor estaba buscando
Aquí está cómo hacer eso:
var query = pets.OrderBy(pet => pet.Name).ThenByDescending(pet => pet.Age);
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 puedes usar OrderBy("Name, Age DESC")
Puedes convertir el IEnumerable a IQueryable.
items = items.AsQueryable().OrderBy("Name ASC");
Sólo tropezé con este viejo ...
Para hacer esto sin la biblioteca LINQ dinámica, solo necesita el código como se muestra a continuación. Esto cubre los escenarios más comunes, incluidas las propiedades anidadas.
Para que funcione con IEnumerable<T>
, podría agregar algunos métodos de envoltura que vayan a través de AsQueryable
, pero el código a continuación es la lógica de Expression
central 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;
}
Edición: se vuelve más divertido si desea mezclar eso con dynamic
, aunque tenga en cuenta que la dynamic
solo se aplica a LINQ-to-Objects (los árboles de expresión para ORM, etc. no pueden representar realmente 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 la 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);
}
}
}
Simplemente construyendo sobre lo que otros han dicho. Encontré 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;
}
Simplemente tropecé con esta pregunta.
Usando la implementación de Marc''s ApplyOrder desde arriba, junté 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 Reflection para obtener cualquier propiedad que quieras clasificar:
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 usar la reflexión es considerablemente más lento que acceder directamente a la propiedad, por lo que el rendimiento tendría que ser investigado.
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;
}
}
Usted podría 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
}
La función GetPropertyValue
es de la respuesta de Kjetil Watnedal
El problema sería ¿por qué? Cualquier clase de este tipo lanzarí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 expresiones, de todos modos se convertirá a SQL para su ejecución.
var result1 = lst.OrderBy(a=>a.Name);// for ascending order.
var result1 = lst.OrderByDescending(a=>a.Name);// for desc order.