2009-11-06 37 views
53

¿Cuál es la forma más simple 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?C# - código para ordenar por una propiedad usando el nombre de la propiedad como una cadena

Lógicamente, esto es lo que me gustaría hacer:

query = query.OrderBy(x => x."ProductId"); 

Actualización: que no especificó originalmente que estoy usando LINQ a Entidades - parece que la reflexión (al menos la GetProperty, Enfoque GetValue) no se traduce a L2E.

+0

Creo que tendrías que usar la reflexión, y no estoy seguro de que puedas usar el reflejo en una expresión lambda ... bueno, casi con certeza no en Linq a SQL, pero tal vez al usar Linq en una lista o algo. – CodeRedick

+0

@Telos: no hay ninguna razón para que no puedas usar la reflexión (o cualquier otra API) en una lambda. Si esto funciona o no si el código se evalúa como una expresión y se traduce a otra cosa (como LINQ-to-SQL, como usted sugiere) es otra cuestión completamente distinta. –

+0

Es por eso que publiqué un comentario en lugar de una respuesta. ;) Mayormente acostumbrado a Linq2SQL ... – CodeRedick

Respuesta

77

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 las llamadas repetidas a la API de reflexión para obtener la propiedad. Ahora la única llamada repetida es obtener el valor.

Sin embargo

Yo estaría a favor de usar un PropertyDescriptor lugar, ya que esto permitirá a medida TypeDescriptor s para ser asignado a su tipo, por lo que es posible tener ligeras operaciones para la recuperación de 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, echa un vistazo a proyecto de Marc Gravel HyperDescriptor 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.

+0

buen punto. Tengo que tener cuidado con esos lambdas – dkackman

+0

Tenga en cuenta que la invocación reflejada (es decir, GetValue) es la parte de reflexión más costosa. Recuperación de metadatos (es decirGetProperty) es en realidad menos costoso (por un orden de magnitud), así que al almacenar en caché esa parte, en realidad no se está ahorrando demasiado. Esto va a costar más o menos lo mismo, y ese costo será muy pesado. Solo algo para tener en cuenta. – jrista

+1

@jrista: la invocación es la más costosa, para estar seguro. Sin embargo, "menos costoso" no significa "gratis", o incluso cercano. La recuperación de metadatos toma una cantidad de tiempo no trivial, por lo que hay una ventaja en el almacenamiento en caché y no * desventaja * (a menos que me falta algo aquí). En verdad, esto realmente debería ser usando un 'PropertyDescriptor' de todos modos (para tener en cuenta los descriptores de tipo personalizados, que * podrían * hacer que la recuperación de valor sea una operación liviana). –

11

Sí, no creo que haya otra manera que Reflection.

Ejemplo:

query = query.OrderBy(x => x.GetType().GetProperty("ProductId").GetValue(x, null)); 
2

La reflexión es la respuesta!

typeof(YourType).GetProperty("ProductId").GetValue(theInstance); 

Hay un montón de cosas que puede hacer para almacenar en caché el PropertyInfo reflejada, comprobar si hay malos cadenas, escriba su función de comparación de consulta, etc., pero en el fondo, esto es lo que haces.

5
query = query.OrderBy(x => x.GetType().GetProperty("ProductId").GetValue(x, null)); 

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

3

Puede usar dynamic Linq - consultar this blog.

También puedes ver this puesto StackOverflow ...

+0

Esta es la mejor respuesta para mí – Demodave

37

Estoy un poco tarde a la fiesta, sin embargo, espero que esto puede ser de alguna ayuda.

El problema con el uso de la reflexión es que el Árbol de expresiones resultante casi con seguridad 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 ejemplo de código siguiente proporciona métodos extention IQueryable para OrderBy y OrderByDescending, y se puede utilizar de este modo:

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

Regards, Mark.

+6

voto para una solución que funciona para EF – icesar

+0

Excelente solución - Estaba buscando exactamente eso. Realmente necesito profundizar en los árboles de Expression. Todavía muy novato en eso. @Mark, ¿alguna solución para hacer expresiones anidadas? Digamos que tengo un tipo T con una propiedad "Sub" de tipo TSub que tiene una propiedad "Valor". Ahora me gustaría obtener la expresión Expresión > para la cadena "Sub.Valor". –

+4

¿Por qué necesitamos 'Expression.Convert' para convertir' propiedad' en 'objeto'? Obtengo un 'No se puede lanzar el tipo' System.String 'para escribir' System.Object '. LINQ to Entities solo admite casting EDM primitive o enumeration types. Error, y su eliminación parece funcionar. – ShuberFu

9

Me gustó la respuesta de @Mark Powell, pero como @ShuberFu dijo, da el error LINQ to Entities only supports casting EDM primitive or enumeration types.

La eliminación de var propAsObject = Expression.Convert(property, typeof(object)); no funcionaba con las propiedades que eran tipos de valores, como enteros, ya que no implícitamente insertaría el int en el 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 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 se debe omitir si se usa Linq a Sql.

Los siguientes trabajos 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) 
      ) 
     ); 
} 
} 
+0

Excelente respuesta, ojalá pudiera votarte dos veces – Anduril

1

más productiva que la extensión de reflexión a los elementos de orden dinámicas:

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

también Dynamic Expressions puede resolver este problema. Puede usar consultas basadas en cadenas a través de expresiones LINQ que se podrían haber construido dinámicamente en tiempo de ejecución.

Cuestiones relacionadas