Supposons que vous ayez des produits et des catégories. Un client dit qu'il est nécessaire d'utiliser d'autres processus métier pour les catégories dont la valeur de notation est supérieure à 50. Vous avez une solide expérience et vous comprenez que demain cette valeur peut être différente - 127,37. Comme vous voulez éviter cette situation, vous écrivez le code de la manière suivante :
public class Category :HasIdBase{ public static readonly Expression > NiceRating =x => x.Rating> 50 ; //... } var niceCategories =db.Query .Where(Category.NiceRating);
Malheureusement, cela ne fonctionnera pas si vous devez sélectionner des produits dans les catégories correspondantes. NiceRating a le type Expression
Ainsi, nous devons convertir Expression
public class Product :HasIdBase{ public virtual Category Category { get ; Positionner; } //... } var niceProductsCompilationError =db.Query .Where(Category.NiceRating);
Heureusement, c'est assez facile !
// En fait, on implémente une composition d'instructions, // qui renvoie l'instruction correspondant à la composition des fonctions cibles public static Expression> Compose ( this Expression > input, Expression > inOutOut) { // c'est le paramètre X => blah-blah. Pour un lambda, nous avons besoin de null var param =Expression.Parameter(typeof(TIn), null); // nous obtenons un objet, auquel cette instruction est appliquée var invoke =Expression.Invoke(input, param); // et exécutez "obtenir un objet et appliquer son instruction" var res =Expression.Invoke(inOutOut, invoke); // renvoie un lambda du type requis return Expression.Lambda >(res, param); } // ajoute une variante "avancée" de Where public static IQueryable Where (this IQueryable queryable, Expression > prop, Expression > où) { return queryable.Where(prop.Compose(where)); } // vérifier [Fait] public void AdvancedWhere_Works() { var product =new Product(new Category() {Rating =700}, "Some Product", 100500); var q =new[] {produit}.AsQueryable(); var valeurs =q.Where(x => x.Category, Category.NiceRating).ToArray(); Assert.Equal(700, valeurs[0].Category.Rating); } Il s'agit de l'implémentation de la composition des instructions dans LinqKit. Cependant, Entity Framework ne fonctionne pas avec InvokeExpression et lève NotSupportedException. Savez-vous que LINQ a des inconvénients ? Pour contourner cette restriction, dans LinqKit, nous utilisons une méthode d'extension AsExpandable. Pete Montgomery a décrit ce problème dans son blog. Sa version de Predicate Builder fonctionne à la fois pour IEnumerable
et IQueryable . Voici le code tel quel.
public static class PredicateBuilder{ ////// Crée un prédicat qui prend la valeur true. /// public static Expression> True () { return param => true ; } /// /// Crée un prédicat qui prend la valeur false. /// public static Expression> False () { return param => false ; } /// /// Crée une expression de prédicat à partir de l'expression lambda spécifiée. /// public static Expression> Create (Expression > predicate) { return predicate ; } /// /// Combine le premier prédicat avec le second en utilisant le "et" logique. /// public static Expression> And (this Expression > first, Expression > second) { return first. Compose(second, Expression.AndAlso); } /// /// Combine le premier prédicat avec le second en utilisant le "ou" logique. /// public static Expression> Or (this Expression > first, Expression > second) { return first. Compose(second, Expression.OrElse); } /// /// Annule le prédicat. /// public static Expression> Not (this Expression > expression) { var negated =Expression.Not(expression.Body); return Expression.Lambda >(nié, expression.Parameters); } /// /// Combine la première expression avec la seconde en utilisant la fonction de fusion spécifiée. /// static ExpressionCompose (this Expression first, Expression second, Func merge) { // zip parameters (map from parameters of second aux paramètres du premier) var map =first.Parameters .Select((f, i) => new { f, s =second.Parameters[i] }) .ToDictionary(p => p.s, p => p.f); // remplace les paramètres de la deuxième expression lambda par les paramètres de la première var secondBody =ParameterRebinder.ReplaceParameters(map, second.Body); // crée une expression lambda fusionnée avec les paramètres de la première expression return Expression.Lambda (merge(first.Body, secondBody), first.Parameters); } class ParameterRebinder :ExpressionVisitor { readonly Dictionary map ; ParameterRebinder(Dictionary map) { this.map =map ?? nouveau Dictionary (); } public static Expression ReplaceParameters(Dictionary map, Expression exp) { return new ParameterRebinder(map).Visit(exp); } protected override Expression VisitParameter(ParameterExpression p) { ParameterExpression replacement ; if (map.TryGetValue(p, out replacement)) { p =replacement; } return base.VisitParameter(p); } }}