¿Cuál es la forma más sencilla de codificar 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 un valor de cadena, por supuesto. ¿Hay alguna manera de usar esa cadena directamente como una propiedad de la consulta linq, sin tener que usar lógica condicional (if / else, switch) 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: originalmente no especifiqué que estoy usando Linq para entidades; parece que la reflexión (al menos el enfoque GetProperty, GetValue) no se traduce a L2E.
fuente
Respuestas:
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
en su lugar, ya que esto permitirá que los correosTypeDescriptor
electrónicos personalizados sean asignados a su tipo, haciendo posible tener operaciones ligeras 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 acelerarlo, consulte el
HyperDescriptor
proyecto de Marc Gravel en CodeProject. He usado esto con gran éxito; es un salvavidas para el enlace de datos de alto rendimiento y las operaciones de propiedad dinámicas en los objetos comerciales.fuente
PropertyDescriptor
todos modos (para tener en cuenta los descriptores de tipo personalizados, lo que podría hacer que la recuperación de valores sea una operación liviana).Llego un poco tarde a la fiesta, sin embargo, espero que esto pueda ser de ayuda.
El problema con el uso de la reflexión es que es casi seguro que el árbol de expresión resultante no será compatible con ningún otro proveedor de Linq que no sea el proveedor interno de .Net. Esto está bien para colecciones internas, sin embargo, esto no funcionará cuando la clasificación se realice en la fuente (ya sea SQL, MongoDb, etc.) antes de la paginación.
El siguiente ejemplo de código proporciona 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.
fuente
Expression.Convert
convertirproperty
aobject
? Recibo unUnable to cast the type 'System.String' to type 'System.Object'. LINQ to Entities only supports casting EDM primitive or enumeration types.
error y parece que la eliminación funciona.var propAsObject = Expression.Convert(property, typeof(object));
y usarproperty
en lugar depropAsObject
LINQ to Entities only supports casting EDM primitive or enumeration types
Me gustó la respuesta de @Mark Powell , pero como dijo @ShuberFu , da el error
LINQ to Entities only supports casting EDM primitive or enumeration types
.La eliminación
var propAsObject = Expression.Convert(property, typeof(object));
no funcionó con propiedades que fueran tipos de valor, como integer, ya que no enmarcaría implícitamente el int al objeto.Usando ideas de Kristofer Andersson y Marc Gravell , encontré una manera de construir la función Queryable usando el nombre de la propiedad y hacer que aún funcione con Entity Framework. También incluí un parámetro opcional IComparer. Precaución: El parámetro IComparer no funciona con Entity Framework y debe omitirse si se usa Linq para 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 to 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) ) ); } }
fuente
Aggregate
fragmento es asombroso! Se encarga de las vistas virtuales creadas a partir del modelo EF Core conJoin
, ya que yo uso propiedades como "T.Property". De lo contrario, ordenar despuésJoin
sería imposible producirInvalidOperationException
o bienNullReferenceException
. Y necesito hacer un pedido DESPUÉSJoin
, porque la mayoría de las consultas son constantes, los pedidos en las vistas no lo son.Aggregate
fragmento. Creo que fue una combinación del código de Marc Gravell y una recomendación intellisense. :)products.OrderBy(x => x.ProductId)
, podrías usarproducts.OrderBy("ProductId")
Sí, no creo que haya otra forma que la Reflexión.
Ejemplo:
query = query.OrderBy(x => x.GetType().GetProperty("ProductId").GetValue(x, null));
fuente
"LINQ to Entities does not recognize the method 'System.Object GetValue(System.Object)' method, and this method cannot be translated into a store expression."
Alguna idea o consejo, por favor?query = query.OrderBy(x => x.GetType().GetProperty("ProductId").GetValue(x, null));
Intento recordar la sintaxis exacta de la parte superior de mi cabeza, pero creo que es correcto.
fuente
¡La reflexión es la respuesta!
typeof(YourType).GetProperty("ProductId").GetValue(theInstance);
Hay muchas cosas que puede hacer para almacenar en caché la PropertyInfo reflejada, verificar cadenas defectuosas, escribir su función de comparación de consultas, etc., pero en el fondo, esto es lo que hace.
fuente
Puede utilizar Linq dinámico: consulte este blog.
También consulte esta publicación de StackOverFlow ...
fuente
Más productivo que la extensión de reflexión a artículos de pedido dinámicos:
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"));
También es posible que necesite almacenar en caché lambas cumplidas (por ejemplo, en el Diccionario <>)
fuente
También las Expresiones Dinámicas pueden resolver este problema. Puede utilizar consultas basadas en cadenas mediante 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)");
fuente
Creo que podemos usar una poderosa herramienta llamada Expresión y, en este caso, usarla como un método de extensión de la siguiente manera:
public static IOrderedQueryable<T> OrderBy<T>(this IQueryable<T> source, string ordering, bool descending) { var type = typeof(T); var property = type.GetProperty(ordering); var parameter = Expression.Parameter(type, "p"); var propertyAccess = Expression.MakeMemberAccess(parameter, property); var orderByExp = Expression.Lambda(propertyAccess, parameter); MethodCallExpression resultExp = Expression.Call(typeof(Queryable), (descending ? "OrderByDescending" : "OrderBy"), new Type[] { type, property.PropertyType }, source.Expression, Expression.Quote(orderByExp)); return (IOrderedQueryable<T>)source.Provider.CreateQuery<T>(resultExp); }
fuente