Angenommen, Sie haben Produkte und Kategorien. Ein Kunde sagt, dass es notwendig ist, andere Geschäftsprozesse für die Kategorien mit einem Bewertungswert von mehr als 50 zu verwenden. Sie haben eine solide Erfahrung und verstehen, dass dieser Wert morgen anders sein kann – 127,37. Um diese Situation zu vermeiden, schreiben Sie den Code folgendermaßen:
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);
Leider funktioniert dies nicht, wenn Sie Produkte aus den entsprechenden Kategorien auswählen müssen. NiceRating hat den Typ Expression
Daher müssen wir Expression
public class Product: HasIdBase<int> { public virtual Category Category { get; set; } //... } var niceProductsCompilationError = db.Query<Product>.Where(Category.NiceRating);
Zum Glück ist es ganz einfach!
// 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); }
Dies ist die Implementierung der Anweisungskomposition in LinqKit. Entity Framework funktioniert jedoch nicht mit InvokeExpression und löst NotSupportedException aus. Wussten Sie, dass LINQ Nachteile hat? Um diese Einschränkung zu umgehen, verwenden wir in LinqKit eine Erweiterungsmethode AsExpandable. Pete Montgomery hat dieses Problem in seinem Blog beschrieben. Seine Version von Predicate Builder funktioniert sowohl für IEnumerable
Hier ist der Code wie er ist.
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); } } }