如何在DDD模式与EF Core中实现悲观并发且分离业务与数据访问层?
业务层与数据访问层分离场景下悲观并发的实现方案
业务逻辑与数据访问层分离的架构下,完全可以正常使用悲观并发,核心实现思路是调整DbContext的生命周期管理逻辑,让DbContext的销毁时机和业务事务的结束时机绑定,而非在单个数据查询方法执行完成后就释放DbContext。
具体实现方案
1. 基于工作单元(Unit Of Work)模式实现(通用方案)
该方案适用于所有类型的项目,分层清晰,完全符合关注点分离原则:
- 定义工作单元接口封装
DbContext与事务控制,作为业务层和数据访问层的中间协调层 - 整个业务操作流程共享同一个工作单元对应的
DbContext实例,悲观锁的生命周期会和事务生命周期保持一致 - 数据访问层(仓储)仅封装数据库操作逻辑,业务层仅处理业务校验和流程控制,互不感知对方的实现细节
示例代码如下:
工作单元接口定义
public interface IUnitOfWork : IDisposable { IRepository<Order> OrderRepository { get; } IRepository<Item> ItemRepository { get; } int SaveChanges(); void BeginTransaction(); void CommitTransaction(); void RollbackTransaction(); }
服务层业务逻辑实现
public Order AddItemToOrder(int orderId, Item paramItem) { using var uow = _unitOfWorkFactory.Create(); uow.BeginTransaction(); // 调用仓储封装的带悲观锁的查询方法,锁会持续到事务结束 var order = uow.OrderRepository.GetOrderWithUpdLock(orderId); // 业务校验完全放在业务层,不需要感知数据库锁逻辑 if(order.Items.Count >= 20) { uow.RollbackTransaction(); throw new InvalidOperationException("订单商品数量不能超过20"); } order.Items.Add(paramItem); uow.SaveChanges(); uow.CommitTransaction(); return order; }
仓储层(数据访问层)实现
public class OrderRepository : IRepository<Order> { private readonly MyDbContext _dbContext; // 注入当前工作单元对应的同一个DbContext实例 public OrderRepository(MyDbContext dbContext) { _dbContext = dbContext; } // 封装带悲观锁的查询逻辑,不同数据库的锁语法差异在这里处理 public Order GetOrderWithUpdLock(int orderId) { var sql = "select * from [Order] where IDOrder = {0} with(UPDLOCK)"; return _dbContext.Orders.FromRawSql(sql, orderId) .Include(x => x.Items) .FirstOrDefault(); } }
2. 基于IoC容器Scoped生命周期实现(Web项目适用)
如果是Web项目,可以直接利用IoC容器的生命周期管理简化实现:
- 将
DbContext注册为Scoped生命周期,同一个请求内共享同一个DbContext实例 - 服务层的业务方法作为同一个请求内的逻辑,直接开启事务后调用仓储的加锁查询、执行业务逻辑、提交事务即可,悲观锁会在事务周期内持续生效
注意事项
- 悲观锁的持有时间要尽可能短,不要在锁持有过程中执行耗时的非数据库操作,避免引发数据库死锁或性能问题
- 不同数据库的悲观锁语法存在差异,比如MySQL用
FOR UPDATE、SQL Server用WITH(UPDLOCK),这部分差异全部封装在数据访问层即可,业务层不需要感知
内容的提问来源于stack exchange,提问作者Álvaro García
相关产品推荐
相关产品推荐

