sql >> Databáze >  >> RDS >> Database

Eliminace duplikace výrazů Where v aplikaci

Předpokládejme, že máte produkty a kategorie. Klient říká, že pro kategorie s hodnotou ratingu vyšší než 50 je nutné použít jiné obchodní procesy. Máte solidní zkušenosti a chápete, že zítra může být tato hodnota jiná – 127,37. Chcete-li se této situaci vyhnout, napíšete kód následujícím způsobem:

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);

Bohužel to nebude fungovat, pokud potřebujete vybrat produkty z odpovídajících kategorií. NiceRating má typ Expression>. V případě produktu budete muset použít Expression>.

Potřebujeme tedy převést Výraz> na Výraz>.

 public class Product: HasIdBase<int>
    {
        public virtual Category Category { get; set; }

       //...
    }

    var niceProductsCompilationError = db.Query<Product>.Where(Category.NiceRating);

Naštěstí je to docela snadné!

 // In fact, we implement a composition of statements, 
         // which returns the statement matching the composition of target functions 
     public static Expression<Func<TIn, TOut>> Compose<TIn, TInOut, TOut>(
             this Expression<Func<TIn, TInOut>> input,
             Expression<Func<TInOut, TOut>> inOutOut)
        {
            // this is the X parameter => blah-blah. For a lambda, we need null
            var param = Expression.Parameter(typeof(TIn), null);
            // we get an object, to which this statement is applied
            var invoke = Expression.Invoke(input, param);
            // and execute “get an object and apply its statement”
            var res = Expression.Invoke(inOutOut, invoke);
            
            // return a lambda of the required type 
            return Expression.Lambda<Func<TIn, TOut>>(res, param);
        }
        
        // add an “advanced” variant of 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));
        }
	
        // check
	[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);
	}

Toto je implementace kompozice příkazů v LinqKit. Entity Framework však nefunguje s InvokeExpression a vyvolá NotSupportedException. Víte, že LINQ má nevýhody? Abychom toto omezení obešli, v LinqKit používáme metodu rozšíření AsExpandable. Pete Montgomery popsal tento problém na svém blogu. Jeho verze Preddicate Builder funguje pro IEnumerable i IQueryable.

Zde je kód tak, jak je.

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 => p.s, p => p.f);
 
        // 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);
        }
    }
}


  1. Existuje způsob, jak procházet proměnnou tabulky v TSQL bez použití kurzoru?

  2. Připojování k Heroku Postgres ze Spring Boot

  3. Najděte v MySQL hodnoty, které neobsahují čísla

  4. nodejs mysql Chyba:Připojení ztraceno Server ukončil připojení