C # – código para ordenar por una propiedad usando el nombre de la propiedad como una cadena

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

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.

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 OrderBy(this IQueryable source, string propertyName) { return source.OrderBy(ToLambda(propertyName)); } public static IOrderedQueryable OrderByDescending(this IQueryable source, string propertyName) { return source.OrderByDescending(ToLambda(propertyName)); } private static Expression> ToLambda(string propertyName) { var parameter = Expression.Parameter(typeof(T)); var property = Expression.Property(parameter, propertyName); var propAsObject = Expression.Convert(property, typeof(object)); return Expression.Lambda>(propAsObject, parameter); } } 

Saludos, Mark.

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 OrderBy(this IQueryable query, string propertyName, IComparer comparer = null) { return CallOrderedQueryable(query, "OrderBy", propertyName, comparer); } public static IOrderedQueryable OrderByDescending(this IQueryable query, string propertyName, IComparer comparer = null) { return CallOrderedQueryable(query, "OrderByDescending", propertyName, comparer); } public static IOrderedQueryable ThenBy(this IOrderedQueryable query, string propertyName, IComparer comparer = null) { return CallOrderedQueryable(query, "ThenBy", propertyName, comparer); } public static IOrderedQueryable ThenByDescending(this IOrderedQueryable query, string propertyName, IComparer comparer = null) { return CallOrderedQueryable(query, "ThenByDescending", propertyName, comparer); } ///  /// Builds the Queryable functions using a TSource property name. ///  public static IOrderedQueryable CallOrderedQueryable(this IQueryable query, string methodName, string propertyName, IComparer comparer = null) { var param = Expression.Parameter(typeof(T), "x"); var body = propertyName.Split('.').Aggregate(param, Expression.PropertyOrField); return comparer != null ? (IOrderedQueryable)query.Provider.CreateQuery( Expression.Call( typeof(Queryable), methodName, new[] { typeof(T), body.Type }, query.Expression, Expression.Lambda(body, param), Expression.Constant(comparer) ) ) : (IOrderedQueryable)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)); 
 query = query.OrderBy(x => x.GetType().GetProperty("ProductId").GetValue(x, null)); 

Tratando de recordar la syntax exacta en la parte superior de mi cabeza, pero creo que es correcto.

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.

Puedes usar Linq dynamic: echa un vistazo a este blog.

También echa un vistazo a esta publicación de StackOverFlow …

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

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(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>(boxer, param).Compile(); return getPropValue(self); } } 

Ejemplo:

 var ordered = items.OrderBy(x => x.GetPropertyDynamic("ProductId"));