如何在规约模式(Specification Pattern)泛型中支持类继承特性?
解决方案
方案1:添加基类规约向下适配能力(侵入性最低,无需修改现有规约)
核心思路是通过表达式参数替换,将针对基类的规约包装为适用于子类的规约,完全兼容EF表达式解析逻辑。
首先添加参数替换访问器(如果现有代码已有可跳过):
internal class ParameterReplaceVisitor : ExpressionVisitor { private readonly ParameterExpression _oldParam; private readonly ParameterExpression _newParam; public ParameterReplaceVisitor(ParameterExpression oldParam, ParameterExpression newParam) { _oldParam = oldParam; _newParam = newParam; } protected override Expression VisitParameter(ParameterExpression node) { return node == _oldParam ? _newParam : base.VisitParameter(node); } }
添加适配用的规约包装类:
internal class AdaptedSpecification<TDerived, TBase> : Specification<TDerived> where TDerived : TBase { private readonly Specification<TBase> _baseSpec; public AdaptedSpecification(Specification<TBase> baseSpec) { _baseSpec = baseSpec; } public override Expression<Func<TDerived, bool>> FilterExpr() { var baseExpr = _baseSpec.FilterExpr(); var derivedParam = Expression.Parameter(typeof(TDerived), baseExpr.Parameters[0].Name); var visitor = new ParameterReplaceVisitor(baseExpr.Parameters[0], derivedParam); var newBody = visitor.Visit(baseExpr.Body); return Expression.Lambda<Func<TDerived, bool>>(newBody, derivedParam); } }
添加规约扩展方法简化调用:
public static class SpecificationExtensions { public static Specification<TDerived> As<TDerived, TBase>(this Specification<TBase> spec) where TDerived : TBase { return new AdaptedSpecification<TDerived, TBase>(spec); } // 重载And方法,无需显式转换即可直接组合父子类规约 public static Specification<TDerived> And<TDerived, TBase>(this Specification<TBase> left, Specification<TDerived> right) where TDerived : TBase { return left.As<TDerived, TBase>().And(right); } }
使用方式:
// 方式1:显式转换后组合 var johnSpec = new NameSpec("John").As<Baby, Person>(); var combinedSpecification = johnSpec.And(new IsAbleToTalkSpec()); // 方式2:直接用重载的And方法,自动适配 var combinedSpecification = new NameSpec("John").And(new IsAbleToTalkSpec()); List<Baby> result = myRepository.Find(combinedSpecification);
方案2:通用泛型规约(更适合大量复用基类属性规约的场景)
将针对基类属性的规约定义为带约束的泛型类,所有子类可直接共用,无需重复实现:
// 改造后的NameSpec,支持所有Person子类使用 public class NameSpec<T> : Specification<T> where T : Person { private readonly string _name; public NameSpec(string name) { _name = name; } public override Expression<Func<T, bool>> FilterExpr() { return p => p.Name == _name; } }
使用方式:
var johnSpec = new NameSpec<Baby>("John"); var combinedSpecification = johnSpec.And(new IsAbleToTalkSpec()); List<Baby> result = myRepository.Find(combinedSpecification);
方案选择建议
- 若已有大量写好的非泛型基类规约,不想修改原有代码,选方案1,侵入性最低
- 若还在规约设计初期,基类属性需要大量给子类复用,选方案2,代码更简洁易维护
两种方案生成的表达式都可被EF正常解析为SQL,不会出现内存过滤的问题。
内容的提问来源于stack exchange,提问作者gst
相关产品推荐
相关产品推荐

