Quel est le moyen le plus simple de coder sur une propriété en C # lorsque j'ai le nom de la propriété sous forme de chaîne? Par exemple, je souhaite autoriser l'utilisateur à ordonner certains résultats de recherche par une propriété de son choix (en utilisant LINQ). Ils choisiront la propriété «Trier par» dans l'interface utilisateur - comme valeur de chaîne bien sûr. Existe-t-il un moyen d'utiliser cette chaîne directement comme propriété de la requête linq, sans avoir à utiliser la logique conditionnelle (if / else, commutateur) pour mapper les chaînes aux propriétés. Réflexion?
Logiquement, c'est ce que j'aimerais faire:
query = query.OrderBy(x => x."ProductId");
Mise à jour: Je n'ai pas spécifié à l'origine que j'utilise Linq to Entities - il semble que la réflexion (au moins l'approche GetProperty, GetValue) ne se traduit pas en L2E.
la source
Réponses:
Je proposerais cette alternative à ce que tout le monde a publié.
System.Reflection.PropertyInfo prop = typeof(YourType).GetProperty("PropertyName"); query = query.OrderBy(x => prop.GetValue(x, null));
Cela évite les appels répétés à l'API de réflexion pour obtenir la propriété. Maintenant, le seul appel répété est d'obtenir la valeur.
pourtant
Je recommanderais
PropertyDescriptor
plutôt d' utiliser un , car cela permettra d'TypeDescriptor
attribuer des s personnalisés à votre type, ce qui rendra possible des opérations légères pour récupérer les propriétés et les valeurs. En l'absence d'un descripteur personnalisé, il reviendra de toute façon à la réflexion.PropertyDescriptor prop = TypeDescriptor.GetProperties(typeof(YourType)).Find("PropertyName"); query = query.OrderBy(x => prop.GetValue(x));
Quant à l'accélérer, consultez le
HyperDescriptor
projet de Marc Gravel sur CodeProject. Je l'ai utilisé avec beaucoup de succès; c'est une bouée de sauvetage pour la liaison de données hautes performances et les opérations de propriétés dynamiques sur les objets métier.la source
PropertyDescriptor
toute façon (pour tenir compte des descripteurs de type personnalisés, ce qui pourrait faire de la récupération de valeur une opération légère).Je suis un peu en retard à la fête, cependant, j'espère que cela peut être utile.
Le problème avec l'utilisation de la réflexion est que l'arborescence d'expressions résultante ne sera presque certainement pas prise en charge par les fournisseurs Linq autres que le fournisseur .Net interne. Cela convient aux collections internes, mais cela ne fonctionnera pas lorsque le tri doit être effectué à la source (que ce soit SQL, MongoDb, etc.) avant la pagination.
L'exemple de code ci-dessous fournit des méthodes d'extension IQueryable pour OrderBy et OrderByDescending, et peut être utilisé comme ceci:
query = query.OrderBy("ProductId");
Méthode d'extension:
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); } }
Cordialement, Mark.
la source
Expression.Convert
de convertirproperty
àobject
? J'obtiens uneUnable to cast the type 'System.String' to type 'System.Object'. LINQ to Entities only supports casting EDM primitive or enumeration types.
erreur et sa suppression semble fonctionner.var propAsObject = Expression.Convert(property, typeof(object));
et utilisez simplementproperty
à la place depropAsObject
LINQ to Entities only supports casting EDM primitive or enumeration types
J'ai aimé la réponse de @Mark Powell , mais comme @ShuberFu l'a dit, cela donne l'erreur
LINQ to Entities only supports casting EDM primitive or enumeration types
.La suppression
var propAsObject = Expression.Convert(property, typeof(object));
ne fonctionnait pas avec les propriétés qui étaient des types valeur, tels que des entiers, car elle ne mettrait pas implicitement l'int en objet.En utilisant les idées de Kristofer Andersson et Marc Gravell, j'ai trouvé un moyen de construire la fonction Queryable en utilisant le nom de la propriété et de la faire fonctionner avec Entity Framework. J'ai également inclus un paramètre IComparer facultatif. Attention: le paramètre IComparer ne fonctionne pas avec Entity Framework et doit être omis si vous utilisez Linq to Sql.
Ce qui suit fonctionne avec Entity Framework et Linq to Sql:
query = query.OrderBy("ProductId");
Et @Simon Scheurer cela fonctionne également:
query = query.OrderBy("ProductCategory.CategoryId");
Et si vous n'utilisez pas Entity Framework ou Linq to Sql, cela fonctionne:
query = query.OrderBy("ProductCategory", comparer);
Voici le code:
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) ) ); } }
la source
Aggregate
fragment est génial! Il prend en charge les vues virtuelles créées à partir du modèle EF Core avecJoin
, puisque j'utilise des propriétés telles que "T.Property". Sinon, commander aprèsJoin
serait impossible de produire soitInvalidOperationException
ouNullReferenceException
. Et je dois commander APRÈSJoin
, car la plupart des requêtes sont constantes, les ordres dans les vues ne le sont pas.Aggregate
fragment. Je crois que c'était une combinaison du code de Marc Gravell et d'une recommandation intellisense. :)products.OrderBy(x => x.ProductId)
, vous pouvez utiliserproducts.OrderBy("ProductId")
Oui, je ne pense pas qu'il y ait d'autre moyen que la réflexion.
Exemple:
query = query.OrderBy(x => x.GetType().GetProperty("ProductId").GetValue(x, null));
la source
"LINQ to Entities does not recognize the method 'System.Object GetValue(System.Object)' method, and this method cannot be translated into a store expression."
Des pensées ou des conseils, s'il vous plaît?query = query.OrderBy(x => x.GetType().GetProperty("ProductId").GetValue(x, null));
Essayer de rappeler la syntaxe exacte du haut de ma tête, mais je pense que c'est correct.
la source
La réflexion est la réponse!
typeof(YourType).GetProperty("ProductId").GetValue(theInstance);
Il y a beaucoup de choses que vous pouvez faire pour mettre en cache le PropertyInfo reflété, vérifier les mauvaises chaînes, écrire votre fonction de comparaison de requêtes, etc., mais au fond, c'est ce que vous faites.
la source
Vous pouvez utiliser Linq dynamique - consultez ce blog.
Consultez également cet article de StackOverFlow ...
la source
Plus productif que l'extension de réflexion aux éléments de commande dynamiques:
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); } }
Exemple:
var ordered = items.OrderBy(x => x.GetPropertyDynamic("ProductId"));
Vous devrez peut-être également mettre en cache les lambas conformes (par exemple dans le dictionnaire <>)
la source
Aussi expressions dynamique peut résoudre ce problème. Vous pouvez utiliser des requêtes basées sur des chaînes via des expressions LINQ qui auraient pu être construites dynamiquement au moment de l'exécution.
var query = query .Where("Category.CategoryName == @0 and Orders.Count >= @1", "Book", 10) .OrderBy("ProductId") .Select("new(ProductName as Name, Price)");
la source
Je pense que nous pouvons utiliser un nom d'outil puissant Expression et dans ce cas, l'utiliser comme méthode d'extension comme suit:
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); }
la source