Giả sử bạn có sản phẩm và danh mục. Một khách hàng nói rằng cần sử dụng các quy trình nghiệp vụ khác cho các danh mục có giá trị xếp hạng cao hơn 50. Bạn có kinh nghiệm vững chắc và bạn hiểu rằng ngày mai giá trị này có thể khác - 127,37. Khi bạn muốn tránh trường hợp này, bạn viết mã theo cách sau:
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);
Thật không may, điều này sẽ không hoạt động nếu bạn cần chọn sản phẩm từ các danh mục tương ứng. NiceRating có kiểu Expression
Do đó, chúng ta cần chuyển đổi Biểu thức
public class Product: HasIdBase<int> { public virtual Category Category { get; set; } //... } var niceProductsCompilationError = db.Query<Product>.Where(Category.NiceRating);
May mắn thay, nó khá dễ dàng!
// 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); }
Đây là việc thực hiện thành phần câu lệnh trong LinqKit. Tuy nhiên, Entity Framework không hoạt động với InvokeExpression và ném NotSupportedException. Bạn có biết rằng LINQ có nhược điểm không? Để khắc phục hạn chế này, trong LinqKit, chúng tôi sử dụng phương thức mở rộng AsExpandable. Pete Montgomery đã mô tả vấn đề này trong blog của mình. Phiên bản Predicate Builder của anh ấy hoạt động cho cả IEnumerable
Đây là mã hiện tại.
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); } } }