仓储模式
本文档中引用的文件
/lmes-template/src/CMS.Plugin.MyPluginName.Domain/MyEntityNames/IMyEntityNameRepository.cs/lmes-template/src/CMS.Plugin.MyPluginName.EntityFrameworkCore/Repositories/EfCoreMyEntityNameRepository.cs/lmes-template/src/CMS.Plugin.MyPluginName.Domain/MyEntityNames/MyEntityName.cs/lmes-template/src/CMS.Plugin.MyPluginName.Domain/MyEntityNames/MyEntityNameSpecification.cs/lmes-template/src/CMS.Plugin.MyPluginName.Application/Implements/MyEntityNameAppService.cs/lmes-template/src/CMS.Plugin.MyPluginName.Application.Contracts/Dtos/MyEntityNames/GetMyEntityNamesInput.cs/lmes-template/src/CMS.Plugin.MyPluginName.EntityFrameworkCore/Extensions/CMSPluginEfCoreExtensions.MyEntityName.cs/lmes-template/src/CMS.Plugin.MyPluginName.EntityFrameworkCore/ICMSPluginDbContext.cs
目录
- 引言
- 仓储模式架构设计
- IMyEntityNameRepository 接口定义
- EfCoreMyEntityNameRepository 实现分析
- 仓储层与领域逻辑的隔离机制
- 查询契约设计意图与使用场景
- 自定义查询构建与EF Core IQueryable集成
- 同步与异步方法选择策略
- 异常处理与日志记录开发建议
- 完整调用链路示意图
引言
仓储接口隔离领域逻辑与持久化实现。本文说明 IMyEntityNameRepository 与 EfCoreMyEntityNameRepository 的查询、事务处理及应用服务调用方式。
仓储模式架构设计
图示来源
/lmes-template/src/CMS.Plugin.MyPluginName/Controller/MyEntityNameController.cs/lmes-template/src/CMS.Plugin.MyPluginName.Application/Implements/MyEntityNameAppService.cs/lmes-template/src/CMS.Plugin.MyPluginName.Domain/MyEntityNames/IMyEntityNameRepository.cs/lmes-template/src/CMS.Plugin.MyPluginName.EntityFrameworkCore/Repositories/EfCoreMyEntityNameRepository.cs/lmes-template/src/CMS.Plugin.MyPluginName.EntityFrameworkCore/ICMSPluginDbContext.cs
IMyEntityNameRepository 接口定义
IMyEntityNameRepository 继承 IBasicRepository<MyEntityName, Guid>,并通过异步方法扩展业务实体的查询契约。
接口主要包含以下方法:
FindByNameAsync: 根据名称查找单个实体NameExistAsync: 检查名称是否已存在(用于唯一性校验)GetMaxSortAsync: 获取当前最大排序值,用于新记录插入时的顺序分配GetListAsync: 支持分页、排序、过滤和规约模式的复合查询GetCountAsync: 获取满足条件的记录总数
这些方法共同构成了对 MyEntityName 实体的数据访问契约,为上层服务提供了统一且语义明确的API。
本节来源
/lmes-template/src/CMS.Plugin.MyPluginName.Domain/MyEntityNames/IMyEntityNameRepository.cs
EfCoreMyEntityNameRepository 实现分析
EfCoreMyEntityNameRepository 是 IMyEntityNameRepository 的具体实现类,继承自 EfCoreRepository<ICMSPluginDbContext, MyEntityName, Guid>,利用ABP框架提供的基础能力完成EF Core集成。
关键实现细节包括:
- 构造函数注入:通过
IDbContextProvider<ICMSPluginDbContext>获取数据库上下文,支持多数据库适配。 - 异步查询实现:所有方法均采用
async/await模式,确保线程高效利用。 - 动态排序支持:使用
System.Linq.Dynamic.Core包实现字符串形式的排序表达式解析。 - 分页处理:通过
PageBy扩展方法实现跳过与限制逻辑。 - 规约模式集成:结合
Volo.Abp.Specifications实现可复用的查询条件封装。
例如,在 GetListAsync 方法中,同时支持 Specification 对象和字符串过滤条件,体现了灵活的查询组合能力。
本节来源
/lmes-template/src/CMS.Plugin.MyPluginName.EntityFrameworkCore/Repositories/EfCoreMyEntityNameRepository.cs
仓储层与领域逻辑的隔离机制
仓储模式的核心价值在于解耦领域模型与数据访问技术。在本项目中,这种隔离通过以下方式实现:
- 接口抽象:
IMyEntityNameRepository定义了领域层所需的数据操作契约,不暴露任何EF Core或SQL相关细节。 - 依赖倒置:应用服务(如
MyEntityNameAppService)仅依赖于仓储接口,而非具体实现,便于单元测试和替换实现。 - 实体保护:
MyEntityName实体的属性设置器为protected,确保状态变更只能通过领域方法(如Update)进行,防止外部直接修改。 - 变更追踪透明化:EF Core自动管理实体状态,仓储层无需显式调用
SaveChanges,由ABP框架的UOW(工作单元)统一提交。
这种设计使得领域逻辑专注于业务规则,而数据持久化细节被封装在基础设施层。
本节来源
/lmes-template/src/CMS.Plugin.MyPluginName.Domain/MyEntityNames/MyEntityName.cs/lmes-template/src/CMS.Plugin.MyPluginName.Application/Implements/MyEntityNameAppService.cs/lmes-template/src/CMS.Plugin.MyPluginName.Domain/MyEntityNames/IMyEntityNameRepository.cs
查询契约设计意图与使用场景
各查询方法的设计意图如下:
| 方法名 | 设计意图 | 使用场景 |
|---|---|---|
FindByNameAsync | 快速定位唯一命名的实体 | 登录验证、配置项查找 |
NameExistAsync | 高效检查名称唯一性 | 创建/更新时的重复校验 |
GetMaxSortAsync | 获取当前最大排序值 | 新记录插入时自动排序 |
GetListAsync | 支持复杂条件的分页查询 | 列表展示、导出、筛选 |
GetCountAsync | 独立获取总数以优化性能 | 分页导航、统计显示 |
其中,GetListAsync 和 GetCountAsync 通常配合使用,实现高效的分页展示。通过分离查询与计数操作,避免了一次性加载全部数据的性能问题。
本节来源
/lmes-template/src/CMS.Plugin.MyPluginName.Domain/MyEntityNames/IMyEntityNameRepository.cs/lmes-template/src/CMS.Plugin.MyPluginName.Application/Implements/MyEntityNameAppService.cs
自定义查询构建与EF Core IQueryable集成
仓储实现充分利用了 IQueryable<T> 的延迟执行特性,支持动态查询构建:
- 规约模式应用:
MyEntityNameSpecification将业务规则封装为可复用的表达式树,在GetListAsync中通过.Where(specification.ToExpression())注入查询条件。 - 条件拼接:使用
.WhereIf()方法实现条件化查询片段添加,如仅在filter不为空时添加名称模糊匹配。 - 动态排序:借助
System.Linq.Dynamic.Core的OrderBy(string)方法,将字符串排序参数转换为表达式。 - 细节包含控制:通过
IncludeDetails(includeDetails)控制是否加载关联数据,优化查询性能。
这种方式允许在不修改仓储代码的前提下,通过参数组合实现多样化的查询需求。
本节来源
/lmes-template/src/CMS.Plugin.MyPluginName.EntityFrameworkCore/Repositories/EfCoreMyEntityNameRepository.cs/lmes-template/src/CMS.Plugin.MyPluginName.Domain/MyEntityNames/MyEntityNameSpecification.cs/lmes-template/src/CMS.Plugin.MyPluginName.EntityFrameworkCore/Extensions/CMSPluginEfCoreExtensions.MyEntityName.cs
同步与异步方法选择策略
本项目中所有仓储方法均采用异步实现,遵循以下策略:
- 默认异步优先:所有I/O操作(数据库查询、插入、更新、删除)均使用
async/await,避免线程阻塞。 - 返回Task而非Task<T>:对于无返回值的操作(如删除),返回
Task而非void,确保调用方可正确等待。 - 取消令牌支持:关键方法接受
CancellationToken参数,支持请求取消和超时控制。 - 避免同步等待:在应用服务中始终使用
await而非.Result或.Wait(),防止死锁。
ABP框架的UOW机制确保即使在异步调用链中,事务也能正确传播和提交。
本节来源
/lmes-template/src/CMS.Plugin.MyPluginName.Domain/MyEntityNames/IMyEntityNameRepository.cs/lmes-template/src/CMS.Plugin.MyPluginName.EntityFrameworkCore/Repositories/EfCoreMyEntityNameRepository.cs
异常处理与日志记录开发建议
仓储层本身不直接处理异常,而是通过以下机制保障可靠性:
- 异常透明传递:数据库异常(如约束冲突、连接失败)由EF Core抛出并向上层传递,由应用服务或控制器统一处理。
- 业务异常封装:在应用服务中使用
UserFriendlyException提供用户可读的错误信息,如名称重复提示。 - 自动日志记录:ABP框架自动记录仓储调用的日志,包括执行时间、SQL语句(开发环境)等。
- 验证前置:在调用仓储前进行输入验证(如
Check.NotNull),减少无效数据库访问。
例如,在 CreateAsync 中先检查名称是否存在,再执行插入,避免了数据库唯一索引冲突异常的产生。
本节来源
/lmes-template/src/CMS.Plugin.MyPluginName.Application/Implements/MyEntityNameAppService.cs/lmes-template/src/CMS.Plugin.MyPluginName.Application/Implements/MyEntityNameAppService.cs
完整调用链路示意图
图示来源
/lmes-template/src/CMS.Plugin.MyPluginName/Controller/MyEntityNameController.cs/lmes-template/src/CMS.Plugin.MyPluginName.Application/Implements/MyEntityNameAppService.cs/lmes-template/src/CMS.Plugin.MyPluginName.EntityFrameworkCore/Repositories/EfCoreMyEntityNameRepository.cs/lmes-template/src/CMS.Plugin.MyPluginName.EntityFrameworkCore/ICMSPluginDbContext.cs