跳到主要内容
版本:Next

仓储模式

本文档中引用的文件

目录​

  1. 引言
  2. 仓储模式架构设计
  3. IMyEntityNameRepository 接口定义
  4. EfCoreMyEntityNameRepository 实现分析
  5. 仓储层与领域逻辑的隔离机制
  6. 查询契约设计意图与使用场景
  7. 自定义查询构建与EF Core IQueryable集成
  8. 同步与异步方法选择策略
  9. 异常处理与日志记录最佳实践
  10. 完整调用链路示意图

引言​

仓储模式(Repository Pattern)在本项目中作为数据访问层的核心设计模式,承担着隔离领域逻辑与持久化细节的重要职责。通过定义清晰的接口契约和具体的EF Core实现,系统实现了高内聚、低耦合的架构目标。本文将深入分析 IMyEntityNameRepository 接口及其在 EfCoreMyEntityNameRepository 中的具体实现,揭示其如何支持灵活查询、事务管理以及与上层应用服务的协作机制。

仓储模式架构设计​

图示来源

IMyEntityNameRepository 接口定义​

IMyEntityNameRepository 接口继承自 IBasicRepository<MyEntityName, Guid>,扩展了针对业务实体的专用查询契约。该接口定义了多个异步方法,体现了现代.NET应用中非阻塞I/O的最佳实践。

接口主要包含以下方法:

  • FindByNameAsync: 根据名称查找单个实体
  • NameExistAsync: 检查名称是否已存在(用于唯一性校验)
  • GetMaxSortAsync: 获取当前最大排序值,用于新记录插入时的顺序分配
  • GetListAsync: 支持分页、排序、过滤和规约模式的复合查询
  • GetCountAsync: 获取满足条件的记录总数

这些方法共同构成了对 MyEntityName 实体的数据访问契约,为上层服务提供了统一且语义明确的API。

本节来源

EfCoreMyEntityNameRepository 实现分析​

EfCoreMyEntityNameRepository 是 IMyEntityNameRepository 的具体实现类,继承自 EfCoreRepository<ICMSPluginDbContext, MyEntityName, Guid>,利用ABP框架提供的基础能力完成EF Core集成。

关键实现细节包括:

  1. 构造函数注入:通过 IDbContextProvider<ICMSPluginDbContext> 获取数据库上下文,支持多数据库适配。
  2. 异步查询实现:所有方法均采用 async/await 模式,确保线程高效利用。
  3. 动态排序支持:使用 System.Linq.Dynamic.Core 包实现字符串形式的排序表达式解析。
  4. 分页处理:通过 PageBy 扩展方法实现跳过与限制逻辑。
  5. 规约模式集成:结合 Volo.Abp.Specifications 实现可复用的查询条件封装。

例如,在 GetListAsync 方法中,同时支持 Specification 对象和字符串过滤条件,体现了灵活的查询组合能力。

本节来源

仓储层与领域逻辑的隔离机制​

仓储模式的核心价值在于解耦领域模型与数据访问技术。在本项目中,这种隔离通过以下方式实现:

  • 接口抽象:IMyEntityNameRepository 定义了领域层所需的数据操作契约,不暴露任何EF Core或SQL相关细节。
  • 依赖倒置:应用服务(如 MyEntityNameAppService)仅依赖于仓储接口,而非具体实现,便于单元测试和替换实现。
  • 实体保护:MyEntityName 实体的属性设置器为 protected,确保状态变更只能通过领域方法(如 Update)进行,防止外部直接修改。
  • 变更追踪透明化:EF Core自动管理实体状态,仓储层无需显式调用 SaveChanges,由ABP框架的UOW(工作单元)统一提交。

这种设计使得领域逻辑专注于业务规则,而数据持久化细节被封装在基础设施层。

本节来源

查询契约设计意图与使用场景​

各查询方法的设计意图如下:

方法名设计意图使用场景
FindByNameAsync快速定位唯一命名的实体登录验证、配置项查找
NameExistAsync高效检查名称唯一性创建/更新时的重复校验
GetMaxSortAsync获取当前最大排序值新记录插入时自动排序
GetListAsync支持复杂条件的分页查询列表展示、导出、筛选
GetCountAsync独立获取总数以优化性能分页导航、统计显示

其中,GetListAsync 和 GetCountAsync 通常配合使用,实现高效的分页展示。通过分离查询与计数操作,避免了一次性加载全部数据的性能问题。

本节来源

自定义查询构建与EF Core IQueryable集成​

仓储实现充分利用了 IQueryable<T> 的延迟执行特性,支持动态查询构建:

  1. 规约模式应用:MyEntityNameSpecification 将业务规则封装为可复用的表达式树,在 GetListAsync 中通过 .Where(specification.ToExpression()) 注入查询条件。
  2. 条件拼接:使用 .WhereIf() 方法实现条件化查询片段添加,如仅在 filter 不为空时添加名称模糊匹配。
  3. 动态排序:借助 System.Linq.Dynamic.Core 的 OrderBy(string) 方法,将字符串排序参数转换为表达式。
  4. 细节包含控制:通过 IncludeDetails(includeDetails) 控制是否加载关联数据,优化查询性能。

这种方式允许在不修改仓储代码的前提下,通过参数组合实现多样化的查询需求。

本节来源

同步与异步方法选择策略​

本项目中所有仓储方法均采用异步实现,遵循以下策略:

  • 默认异步优先:所有I/O操作(数据库查询、插入、更新、删除)均使用 async/await,避免线程阻塞。
  • 返回Task而非Task<T>:对于无返回值的操作(如删除),返回 Task 而非 void,确保调用方可正确等待。
  • 取消令牌支持:关键方法接受 CancellationToken 参数,支持请求取消和超时控制。
  • 避免同步等待:在应用服务中始终使用 await 而非 .Result 或 .Wait(),防止死锁。

ABP框架的UOW机制确保即使在异步调用链中,事务也能正确传播和提交。

本节来源

异常处理与日志记录最佳实践​

仓储层本身不直接处理异常,而是通过以下机制保障可靠性:

  • 异常透明传递:数据库异常(如约束冲突、连接失败)由EF Core抛出并向上层传递,由应用服务或控制器统一处理。
  • 业务异常封装:在应用服务中使用 UserFriendlyException 提供用户可读的错误信息,如名称重复提示。
  • 自动日志记录:ABP框架自动记录仓储调用的日志,包括执行时间、SQL语句(开发环境)等。
  • 验证前置:在调用仓储前进行输入验证(如 Check.NotNull),减少无效数据库访问。

例如,在 CreateAsync 中先检查名称是否存在,再执行插入,避免了数据库唯一索引冲突异常的产生。

本节来源

完整调用链路示意图​

图示来源