numeros - C#- código para ordenar por una propiedad usando el nombre de la propiedad como una cadena
ordenar nombres alfabeticamente en c# (9)
¿Cuál es la forma más sencilla de codificar contra una propiedad en C # cuando tengo el nombre de la propiedad como una cadena? Por ejemplo, quiero permitir que el usuario ordene algunos resultados de búsqueda por una propiedad de su elección (usando LINQ). Elegirán la propiedad "ordenar por" en la interfaz de usuario, como valor de cadena, por supuesto. ¿Hay alguna manera de utilizar esa cadena directamente como una propiedad de la consulta linq, sin tener que usar la lógica condicional (if / else, cambiar) para asignar las cadenas a las propiedades. ¿Reflexión?
Lógicamente, esto es lo que me gustaría hacer:
query = query.OrderBy(x => x."ProductId");
Actualización: no especifiqué originalmente que estoy usando Linq para Entidades: parece que la reflexión (al menos, el enfoque GetProperty, GetValue) no se traduce a L2E.
La reflexión es la respuesta!
typeof(YourType).GetProperty("ProductId").GetValue(theInstance);
Hay muchas cosas que puede hacer para almacenar en caché el PropertyInfo reflejado, comprobar si hay cadenas defectuosas, escribir su función de comparación de consultas, etc., pero en esencia, esto es lo que hace.
Más productivo que la extensión de reflexión para artículos de orden dinámica:
public static class DynamicExtentions
{
public static object GetPropertyDynamic<Tobj>(this Tobj self, string propertyName) where Tobj : class
{
var param = Expression.Parameter(typeof(Tobj), "value");
var getter = Expression.Property(param, propertyName);
var boxer = Expression.TypeAs(getter, typeof(object));
var getPropValue = Expression.Lambda<Func<Tobj, object>>(boxer, param).Compile();
return getPropValue(self);
}
}
Ejemplo:
var ordered = items.OrderBy(x => x.GetPropertyDynamic("ProductId"));
Me gustó la respuesta de @Mark Powell , pero como dijo @ShuberFu , da el error de LINQ to Entities only supports casting EDM primitive or enumeration types
.
Eliminando var propAsObject = Expression.Convert(property, typeof(object));
no funcionó con propiedades que eran tipos de valores, como enteros, ya que no bloquearía implícitamente el int al objeto.
Usando Ideas de Kristofer Andersson y Marc Gravell , encontré una forma de construir la función Queryable usando el nombre de la propiedad y hacer que funcione con Entity Framework. También incluí un parámetro opcional de IComparer. Precaución: El parámetro IComparer no funciona con Entity Framework y debe omitirse si se usa Linq en Sql.
Lo siguiente funciona con Entity Framework y Linq to Sql:
query = query.OrderBy("ProductId");
Y @Simon Scheurer esto también funciona:
query = query.OrderBy("ProductCategory.CategoryId");
Y si no está utilizando Entity Framework o Linq a Sql, esto funciona:
query = query.OrderBy("ProductCategory", comparer);
Aquí está el código:
public static class IQueryableExtensions
{
public static IOrderedQueryable<T> OrderBy<T>(this IQueryable<T> query, string propertyName, IComparer<object> comparer = null)
{
return CallOrderedQueryable(query, "OrderBy", propertyName, comparer);
}
public static IOrderedQueryable<T> OrderByDescending<T>(this IQueryable<T> query, string propertyName, IComparer<object> comparer = null)
{
return CallOrderedQueryable(query, "OrderByDescending", propertyName, comparer);
}
public static IOrderedQueryable<T> ThenBy<T>(this IOrderedQueryable<T> query, string propertyName, IComparer<object> comparer = null)
{
return CallOrderedQueryable(query, "ThenBy", propertyName, comparer);
}
public static IOrderedQueryable<T> ThenByDescending<T>(this IOrderedQueryable<T> query, string propertyName, IComparer<object> comparer = null)
{
return CallOrderedQueryable(query, "ThenByDescending", propertyName, comparer);
}
/// <summary>
/// Builds the Queryable functions using a TSource property name.
/// </summary>
public static IOrderedQueryable<T> CallOrderedQueryable<T>(this IQueryable<T> query, string methodName, string propertyName,
IComparer<object> comparer = null)
{
var param = Expression.Parameter(typeof(T), "x");
var body = propertyName.Split(''.'').Aggregate<string, Expression>(param, Expression.PropertyOrField);
return comparer != null
? (IOrderedQueryable<T>)query.Provider.CreateQuery(
Expression.Call(
typeof(Queryable),
methodName,
new[] { typeof(T), body.Type },
query.Expression,
Expression.Lambda(body, param),
Expression.Constant(comparer)
)
)
: (IOrderedQueryable<T>)query.Provider.CreateQuery(
Expression.Call(
typeof(Queryable),
methodName,
new[] { typeof(T), body.Type },
query.Expression,
Expression.Lambda(body, param)
)
);
}
}
Sí, no creo que haya otra manera que Reflexión.
Ejemplo:
query = query.OrderBy(x => x.GetType().GetProperty("ProductId").GetValue(x, null));
Sin embargo, llegué un poco tarde a la fiesta, espero que esto pueda ser de alguna ayuda.
El problema con el uso de la reflexión es que el Árbol de Expresión resultante casi con certeza no será admitido por ningún proveedor de Linq que no sea el proveedor interno de .Net. Esto está bien para las colecciones internas, sin embargo, esto no funcionará cuando la clasificación deba hacerse en el origen (ya sea SQL, MongoDb, etc.) antes de la paginación.
El siguiente ejemplo de código proporciona los métodos de extensión IQueryable para OrderBy y OrderByDescending, y se puede usar así:
query = query.OrderBy("ProductId");
Método de extensión:
public static class IQueryableExtensions
{
public static IOrderedQueryable<T> OrderBy<T>(this IQueryable<T> source, string propertyName)
{
return source.OrderBy(ToLambda<T>(propertyName));
}
public static IOrderedQueryable<T> OrderByDescending<T>(this IQueryable<T> source, string propertyName)
{
return source.OrderByDescending(ToLambda<T>(propertyName));
}
private static Expression<Func<T, object>> ToLambda<T>(string propertyName)
{
var parameter = Expression.Parameter(typeof(T));
var property = Expression.Property(parameter, propertyName);
var propAsObject = Expression.Convert(property, typeof(object));
return Expression.Lambda<Func<T, object>>(propAsObject, parameter);
}
}
Saludos, Mark.
También las expresiones dinámicas pueden resolver este problema. Puede usar consultas basadas en cadenas a través de expresiones LINQ que podrían haberse construido dinámicamente en tiempo de ejecución.
var query = query
.Where("Category.CategoryName == @0 and Orders.Count >= @1", "Book", 10)
.OrderBy("ProductId")
.Select("new(ProductName as Name, Price)");
Yo ofrecería esta alternativa a lo que todos los demás han publicado.
System.Reflection.PropertyInfo prop = typeof(YourType).GetProperty("PropertyName");
query = query.OrderBy(x => prop.GetValue(x, null));
Esto evita llamadas repetidas a la API de reflexión para obtener la propiedad. Ahora la única llamada repetida es obtener el valor.
sin embargo
Yo recomendaría usar un PropertyDescriptor
lugar, ya que esto permitirá TypeDescriptor
s personalizados a su tipo, lo que permite tener operaciones livianas para recuperar propiedades y valores. En ausencia de un descriptor personalizado, volverá a la reflexión de todos modos.
PropertyDescriptor prop = TypeDescriptor.GetProperties(typeof(YourType)).Find("PropertyName");
query = query.OrderBy(x => prop.GetValue(x));
En cuanto a la aceleración, echa un vistazo al proyecto HyperDescriptor
Marc Gravel en CodeProject. Lo he usado con gran éxito; es un salvavidas para el enlace de datos de alto rendimiento y las operaciones de propiedades dinámicas en objetos comerciales.
query = query.OrderBy(x => x.GetType().GetProperty("ProductId").GetValue(x, null));
Tratando de recordar la sintaxis exacta en la parte superior de mi cabeza, pero creo que es correcto.