Stel dat u producten en categorieën heeft. Een klant zegt dat het nodig is om andere bedrijfsprocessen te gebruiken voor de categorieën met de beoordelingswaarde hoger dan 50. Je hebt een solide ervaring en je begrijpt dat deze waarde morgen anders kan zijn - 127,37. Omdat je deze situatie wilt vermijden, schrijf je de code op de volgende manier:
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);
Helaas werkt dit niet als u producten uit de overeenkomstige categorieën moet selecteren. NiceRating heeft het type Expression
We moeten dus Expression
public class Product: HasIdBase<int> { public virtual Category Category { get; set; } //... } var niceProductsCompilationError = db.Query<Product>.Where(Category.NiceRating);
Gelukkig is het vrij eenvoudig!
// 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); }
Dit is de implementatie van statement samenstelling in LinqKit. Entity Framework werkt echter niet met InvokeExpression en genereert NotSupportedException. Weet je dat LINQ nadelen heeft? Om deze beperking te omzeilen, gebruiken we in LinqKit een uitbreidingsmethode AsExpandable. Pete Montgomery beschreef dit probleem in zijn blog. Zijn versie van Predicate Builder werkt zowel voor IEnumerable
Hier is de code zoals hij 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 => 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); } } }