📜 ⬆️ ⬇️

Eliminate Where Expressions Duplication in an Application

Suppose you have products and categories. At some point, the client reports that for categories with a rating of> 50, it is necessary to use other business processes. You have enough experience and you understand that where today is 50 tomorrow will be 127.37 and you want to avoid the appearance of magic numbers in the code, so do this:

public class Category : HasIdBase<int> { public static readonly Expression<Func<Category, bool>> NiceRating = x => x.Rating > 50; //... } var niceCategories = db.Query<Category>.Where(Category.NiceRating); 

Unfortunately, this number will not work if you want to select products from the respective categories, because NiceRating is of type Expression<Func<Category, bool>> , and in the case of Product we will need Expression<Func<Product, bool>> . That is, it is necessary to perform the conversion Expression<Func<Category, bool>> => Expression<Func<Product, bool>> .

  public class Product: HasIdBase<int> { public virtual Category Category { get; set; } //... } var niceProductsCompilationError = db.Query<Product>.Where(Category.NiceRating); //  ! 

Fortunately, it's pretty easy to do!

  //     , //    ,     public static Expression<Func<TIn, TOut>> Compose<TIn, TInOut, TOut>( this Expression<Func<TIn, TInOut>> input, Expression<Func<TInOut, TOut>> inOutOut) { //   x => blah-blah.     null var param = Expression.Parameter(typeof(TIn), null); //  ,     var invoke = Expression.Invoke(input, param); //   "       " var res = Expression.Invoke(inOutOut, invoke); //     return Expression.Lambda<Func<TIn, TOut>>(res, param); } //  ""  Where public static IQueryable<T> Where<T, TParam>(this IQueryable<T> queryable, Expression<Func<T, TParam>> prop, Expression<Func<TParam, bool>> where) { return queryable.Where(prop.Compose(where)); } //  [Fact] public void AdvancedWhere_Works() { var product = new Product(new Category() {Rating = 700}, "Some Product", 100500); var q = new[] {product}.AsQueryable(); var values = q.Where(x => x.Category, Category.NiceRating).ToArray(); Assert.Equal(700, values[0].Category.Rating); } 

This is how the composition of expressions in LinqKit is implemented . However, the Entity Framework is not friendly with InvokeExpression and throws a NotSupportedException . Did you know that LINQ is leaking ? To get around this limitation, LinqKit uses the hack AsExpandable extension AsExpandable . This problem was described by Pete Montgomery in his blog . Its version of Predicate Builder does not require special street magic and works for both IEnumerable<T> and IQueryable<T> .
')
I give the code as is.
 public static class PredicateBuilder { /// <summary> /// Creates a predicate that evaluates to true. /// </summary> public static Expression<Func<T, bool>> True<T>() { return param => true; } /// <summary> /// Creates a predicate that evaluates to false. /// </summary> public static Expression<Func<T, bool>> False<T>() { return param => false; } /// <summary> /// Creates a predicate expression from the specified lambda expression. /// </summary> public static Expression<Func<T, bool>> Create<T>(Expression<Func<T, bool>> predicate) { return predicate; } /// <summary> /// Combines the first predicate with the second using the logical "and". /// </summary> public static Expression<Func<T, bool>> And<T>(this Expression<Func<T, bool>> first, Expression<Func<T, bool>> second) { return first.Compose(second, Expression.AndAlso); } /// <summary> /// Combines the first predicate with the second using the logical "or". /// </summary> public static Expression<Func<T, bool>> Or<T>(this Expression<Func<T, bool>> first, Expression<Func<T, bool>> second) { return first.Compose(second, Expression.OrElse); } /// <summary> /// Negates the predicate. /// </summary> public static Expression<Func<T, bool>> Not<T>(this Expression<Func<T, bool>> expression) { var negated = Expression.Not(expression.Body); return Expression.Lambda<Func<T, bool>>(negated, expression.Parameters); } /// <summary> /// Combines the first expression with the second using the specified merge function. /// </summary> static Expression<T> Compose<T>(this Expression<T> first, Expression<T> second, Func<Expression, Expression, Expression> merge) { // zip parameters (map from parameters of second to parameters of first) var map = first.Parameters .Select((f, i) => new { f, s = second.Parameters[i] }) .ToDictionary(p => ps, p => pf); // replace parameters in the second lambda expression with the parameters in the first var secondBody = ParameterRebinder.ReplaceParameters(map, second.Body); // create a merged lambda expression with parameters from the first expression return Expression.Lambda<T>(merge(first.Body, secondBody), first.Parameters); } class ParameterRebinder : ExpressionVisitor { readonly Dictionary<ParameterExpression, ParameterExpression> map; ParameterRebinder(Dictionary<ParameterExpression, ParameterExpression> map) { this.map = map ?? new Dictionary<ParameterExpression, ParameterExpression>(); } public static Expression ReplaceParameters(Dictionary<ParameterExpression, ParameterExpression> 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); } } } 

Source: https://habr.com/ru/post/313394/


All Articles