上篇實現基於 Func<T,bool> 委托,適合記憶體內業務邏輯。但若需傳遞給 LINQ Provider(如 Entity Framework),必須構建 Expression<Func<T,bool>> 表達式樹。
public static Expression<Func<T, bool>> Not<T>(
this Expression<Func<T, bool>> expr) {
var param = expr.Parameters[0];
var body = Expression.Not(expr.Body);
return Expression.Lambda<Func<T, bool>>(body, param);
}
測試:f = i => i % 2 == 0; f.Not() 產生奇數過濾。
直接合併 Expression.And(left.Body, right.Body) 會失敗,因兩棵樹的 ParameterExpression 不是同一物件。
internal class ParameterReplacer : ExpressionVisitor {
private readonly ParameterExpression _target;
public ParameterReplacer(ParameterExpression target) => _target = target;
public Expression Replace(Expression expr) => Visit(expr);
protected override Expression VisitParameter(ParameterExpression p) => _target;
}
public static Expression<Func<T, bool>> And<T>(
this Expression<Func<T, bool>> left,
Expression<Func<T, bool>> right) {
var p = Expression.Parameter(typeof(T), "candidate");
var replacer = new ParameterReplacer(p);
var newLeft = replacer.Replace(left.Body);
var newRight = replacer.Replace(right.Body);
return Expression.Lambda<Func<T, bool>>(
Expression.AndAlso(newLeft, newRight), p);
}
// Or 同理,用 Expression.OrElse
Expression<Func<int, bool>> f = i => i % 2 == 0;
f = f.Not().And(i => i % 3 == 0).Or(i => i % 4 == 0);
// 對應邏輯:(非偶數 且 3的倍數) 或 4的倍數
// 1..6 結果:3, 4
DAO 直接暴露 Expression<Func<T,bool>> 過於自由,易傳入不支援或低效表達式。建議封裝 Criteria 基類:
public abstract class ProductCriteria {
protected ProductCriteria(Expression<Func<Product,bool>> q) => Query = q;
public Expression<Func<Product,bool>> Query { get; }
}
public class ProductIdEqCriteria : ProductCriteria {
public ProductIdEqCriteria(int id) : base(p => p.ProductID == id) { }
}
public class ProductViewRangeCriteria : ProductCriteria {
public ProductViewRangeCriteria(int min, int max)
: base(p => p.ViewCount > min && p.ViewCount < max) { }
}
配合擴展方法 And/Or/Not,兼具類型安全與靈活組合。