规约Specification模式
先从下面的一个示例引入我们要介绍的内容。User没有什么特殊,一个普通的领域对象,它提供了一些查询方法。
public class User { public string ID { get; set; } public string FirstName { get; set; } public string LastName { get; set; } public string Department { get; set; }
public int Age { get; set; }
public List<User> GetUserName(string name) {}
publicList<User> GetUserDepartment(string department) {}
}
在上面的例子中,有什么问题呢,如果需要添加新的过滤条件,按不同的属性做条件进行过滤。会想到怎样处理呢?
增加一些新的查询方法?或是修改当前的方法?
我们来介绍下规约模式
將一些领域知识封装到一个同一单元 --规约--,提升 可重用,可维护,和松耦合的能力。通过bool逻辑將业务规则链接在一起,实现重组业务规则。
定义规约
public interface ISpecification<T> { public bool IsSatisfiedBy(T u); }
public class UserDepartmentSpecification : ISpecification<User> { private readonly string _department; public EmployeeDepartmentSpecification(string depatrment) { _department = depatrment; } public bool IsSatisfiedBy(User employee) { return employee.Department.Equals(_department); } } public class UserAgeSpecification : ISpecification<User> { private readonly int _age; public EmployeeYearSpecification(int age) { _age= age; } public bool IsSatisfiedBy(User employee) { return employee.Aga == _age; } }
Service:
public class UserService { public static List<User> GetUserBy(ISpecification<User> specification, List<User> user) { List<User> NeededUsers= new List<User>(); foreach (var u in user) { if (specification.IsSatisfiedBy(u)) { NeededUsers.Add(u); } } return NeededUsers ; } }
通过规约模式,我们移除了大量最对性的查询方法。如果还需要针对其他属性的过滤条件,也只需要新增新的规约。保持了模型的稳定。
使用 LINQ类别(Orm)的仓储查询
这里我们拿ESHOPWeb 的规约实现学习,核心接口的定义如下图。
ISpecification
public interface ISpecification<T, TResult> : ISpecification<T> { Expression<Func<T, TResult>>? Selector { get; } } public interface ISpecification<T> { IEnumerable<Expression<Func<T, bool>>> WhereExpressions { get; } IEnumerable<(Expression<Func<T, object>> KeySelector, OrderTypeEnum OrderType)> OrderExpressions { get; } IEnumerable<IIncludeAggregator> IncludeAggregators { get; } IEnumerable<string> IncludeStrings { get; } IEnumerable<(Expression<Func<T, string>> Selector, string SearchTerm, int SearchGroup)> SearchCriterias { get; } int? Take { get; } int? Skip { get; } [Obsolete] bool IsPagingEnabled { get; } bool CacheEnabled { get; } string? CacheKey { get; } } public abstract class Specification<T, TResult> : Specification<T>, ISpecification<T, TResult> { protected new virtual ISpecificationBuilder<T, TResult> Query { get; } protected Specification() : base() { this.Query = new SpecificationBuilder<T, TResult>(this); } public Expression<Func<T, TResult>>? Selector { get; internal set; } } public abstract class Specification<T> : ISpecification<T> { protected virtual ISpecificationBuilder<T> Query { get; } protected Specification() { this.Query = new SpecificationBuilder<T>(this); } public IEnumerable<Expression<Func<T, bool>>> WhereExpressions { get; } = new List<Expression<Func<T, bool>>>(); public IEnumerable<(Expression<Func<T, object>> KeySelector, OrderTypeEnum OrderType)> OrderExpressions { get; } = new List<(Expression<Func<T, object>> KeySelector, OrderTypeEnum OrderType)>(); public IEnumerable<IIncludeAggregator> IncludeAggregators { get; } = new List<IIncludeAggregator>(); public IEnumerable<string> IncludeStrings { get; } = new List<string>(); public IEnumerable<(Expression<Func<T, string>> Selector, string SearchTerm, int SearchGroup)> SearchCriterias { get; } = new List<(Expression<Func<T, string>> Selector, string SearchTerm, int SearchGroup)>(); public int? Take { get; internal set; } = null; public int? Skip { get; internal set; } = null; public bool IsPagingEnabled { get; internal set; } = false; public string? CacheKey { get; internal set; } public bool CacheEnabled { get; internal set; } }
SpecificationBuilder
public interface ISpecificationBuilder<T, TResult> : ISpecificationBuilder<T> { new Specification<T, TResult> Specification { get; } } public interface ISpecificationBuilder<T> { Specification<T> Specification { get; } } public class SpecificationBuilder<T, TResult> : SpecificationBuilder<T>, ISpecificationBuilder<T, TResult> { public new Specification<T, TResult> Specification { get; } public SpecificationBuilder(Specification<T, TResult> specification) : base(specification) { this.Specification = specification; } } public class SpecificationBuilder<T> : ISpecificationBuilder<T> { public Specification<T> Specification { get; } public SpecificationBuilder(Specification<T> specification) { this.Specification = specification; } }
ISpecificationEvaluator 会根据规约生成对应的查询表达式
实现与调用
自定义规约的时候实现ISpecification,通过 Query属性( ISpecificationBuilder),构建查询。
public class BlogWithPostsAndAuthorSpec : Specification<Blog> { public BlogWithPostsAndAuthorSpec(int id) { Query.Where(b => b.Id == id) .Include(b => b.Posts) .ThenInclude(p => p.Author); } }
在仓储里,通过 ISpecificationEvaluator<T> 处理查询表达式,返回IQueryable<T>
public abstract class RepositoryBase<T> : IRepositoryBase<T> where T : class { ......
......
private readonly ISpecificationEvaluator<T> specificationEvaluator;
public async Task<List<T>> ListAsync(ISpecification<T> specification) { return await ApplySpecification(specification).ToListAsync(); } public async Task<List<TResult>> ListAsync<TResult>(ISpecification<T, TResult> specification) { return await ApplySpecification(specification).ToListAsync(); } public async Task<int> CountAsync(ISpecification<T> specification) { return await ApplySpecification(specification).CountAsync(); } protected IQueryable<T> ApplySpecification(ISpecification<T> specification) { return specificationEvaluator.GetQuery(dbContext.Set<T>().AsQueryable(), specification); } protected IQueryable<TResult> ApplySpecification<TResult>(ISpecification<T, TResult> specification) { if (specification is null) throw new ArgumentNullException("Specification is required"); if (specification.Selector is null) throw new SelectorNotFoundException(); return specificationEvaluator.GetQuery(dbContext.Set<T>().AsQueryable(), specification); } }
作者:RunStone
出处:https://www.cnblogs.com/RunStone/p/14285023.html
版权:本作品采用「署名-非商业性使用-相同方式共享 4.0 国际」许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构