Database
 sql >> Base de données >  >> RDS >> Database

Éliminer la duplication des expressions Where dans l'application

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>. Dans le cas de Product, vous devrez utiliser Expression>.

Ainsi, nous devons convertir Expression> en 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 Expression Compose(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); } }}