Qual é a maneira mais simples de codificar em relação a uma propriedade em C # quando tenho o nome da propriedade como uma string? Por exemplo, quero permitir que o usuário ordene alguns resultados da pesquisa por uma propriedade de sua escolha (usando LINQ). Eles escolherão a propriedade "ordenar por" na IU - como um valor de string, é claro. Existe uma maneira de usar essa string diretamente como uma propriedade da consulta linq, sem ter que usar lógica condicional (if / else, switch) para mapear as strings para propriedades. Reflexão?
Logicamente, é o que eu gostaria de fazer:
query = query.OrderBy(x => x."ProductId");
Atualização: originalmente não especifiquei que estou usando Linq to Entities - parece que a reflexão (pelo menos a abordagem GetProperty, GetValue) não se traduz em L2E.
fonte
Respostas:
Eu ofereceria essa alternativa ao que todo mundo postou.
System.Reflection.PropertyInfo prop = typeof(YourType).GetProperty("PropertyName"); query = query.OrderBy(x => prop.GetValue(x, null));
Isso evita chamadas repetidas para a API de reflexão para obter a propriedade. Agora, a única chamada repetida é obter o valor.
Contudo
Eu recomendaria usar um em
PropertyDescriptor
vez disso, pois isso permitirá queTypeDescriptor
s personalizados sejam atribuídos ao seu tipo, tornando possível ter operações leves para recuperar propriedades e valores. Na ausência de um descritor personalizado, ele voltará a ser refletido de qualquer maneira.PropertyDescriptor prop = TypeDescriptor.GetProperties(typeof(YourType)).Find("PropertyName"); query = query.OrderBy(x => prop.GetValue(x));
Para acelerar, confira o
HyperDescriptor
projeto de Marc Gravel no CodeProject. Usei isso com grande sucesso; é um salva-vidas para vinculação de dados de alto desempenho e operações de propriedade dinâmica em objetos de negócios.fonte
PropertyDescriptor
qualquer maneira (para levar em conta os descritores de tipo personalizados, o que poderia tornar a recuperação de valor uma operação leve).Estou um pouco atrasado para a festa, no entanto, espero que isso possa ajudar.
O problema de usar reflexão é que a árvore de expressão resultante quase certamente não será suportada por nenhum provedor Linq diferente do provedor .Net interno. Isso é bom para coleções internas, no entanto, não funcionará onde a classificação deve ser feita na origem (seja SQL, MongoDb etc.) antes da paginação.
O exemplo de código abaixo fornece métodos de extensão IQueryable para OrderBy e OrderByDescending e pode ser usado da seguinte forma:
query = query.OrderBy("ProductId");
Método de extensão:
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); } }
Atenciosamente, Mark.
fonte
Expression.Convert
converterproperty
paraobject
? Estou recebendo umUnable to cast the type 'System.String' to type 'System.Object'. LINQ to Entities only supports casting EDM primitive or enumeration types.
erro e a remoção parece funcionar.var propAsObject = Expression.Convert(property, typeof(object));
e apenas useproperty
no lugar depropAsObject
LINQ to Entities only supports casting EDM primitive or enumeration types
Gostei da resposta de @Mark Powell , mas como disse @ShuberFu , dá o erro
LINQ to Entities only supports casting EDM primitive or enumeration types
.A remoção
var propAsObject = Expression.Convert(property, typeof(object));
não funcionou com propriedades que eram tipos de valor, como inteiro, pois não encaixaria implicitamente o int para o objeto.Usando ideias de Kristofer Andersson e Marc Gravell , descobri uma maneira de construir a função Queryable usando o nome da propriedade e fazê-la funcionar com o Entity Framework. Eu também incluí um parâmetro opcional IComparer. Cuidado: O parâmetro IComparer não funciona com o Entity Framework e deve ser deixado de fora se estiver usando Linq para Sql.
O seguinte funciona com Entity Framework e Linq to Sql:
query = query.OrderBy("ProductId");
E @Simon Scheurer também funciona:
query = query.OrderBy("ProductCategory.CategoryId");
E se você não estiver usando o Entity Framework ou Linq to Sql, isso funciona:
query = query.OrderBy("ProductCategory", comparer);
Aqui está o 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) ) ); } }
fonte
Aggregate
fragmento é incrível! Ele cuida das visualizações virtuais criadas a partir do modelo EF Core comJoin
, já que uso propriedades como "T.Property". Caso contrário,Join
seria impossível fazer o pedido depois de produzirInvalidOperationException
ouNullReferenceException
. E eu preciso fazer o pedido DEPOISJoin
, porque a maioria das consultas são constantes, os pedidos nas visualizações não são.products.OrderBy(x => x.ProductId)
, você pode usarproducts.OrderBy("ProductId")
Sim, eu não acho que haja outra maneira além da reflexão.
Exemplo:
query = query.OrderBy(x => x.GetType().GetProperty("ProductId").GetValue(x, null));
fonte
"LINQ to Entities does not recognize the method 'System.Object GetValue(System.Object)' method, and this method cannot be translated into a store expression."
Alguma opinião ou conselho, por favor?query = query.OrderBy(x => x.GetType().GetProperty("ProductId").GetValue(x, null));
Estou tentando lembrar a sintaxe exata de início, mas acho que está correto.
fonte
A reflexão é a resposta!
typeof(YourType).GetProperty("ProductId").GetValue(theInstance);
Há muitas coisas que você pode fazer para armazenar em cache o PropertyInfo refletido, verificar se há strings incorretas, escrever sua função de comparação de consulta, etc., mas, no fundo, é isso que você faz.
fonte
Você pode usar o Linq dinâmico - confira este blog.
Verifique também esta postagem StackOverFlow ...
fonte
Mais produtivo do que a extensão de reflexão para itens 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); } }
Exemplo:
var ordered = items.OrderBy(x => x.GetPropertyDynamic("ProductId"));
Você também pode precisar armazenar em cache lambas em conformidade (por exemplo, no Dicionário <>)
fonte
Além disso, as expressões dinâmicas podem resolver este problema. Você pode usar consultas baseadas em string por meio de expressões LINQ que poderiam ter sido construídas dinamicamente em tempo de execução.
var query = query .Where("Category.CategoryName == @0 and Orders.Count >= @1", "Book", 10) .OrderBy("ProductId") .Select("new(ProductName as Name, Price)");
fonte
Acho que podemos usar um nome de ferramenta poderoso Expression e, neste caso, usá-lo como um método de extensão da seguinte maneira:
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); }
fonte