在ABP Framework 5.3中如何用EF Core实现表并发检查及关联表保护?
针对ABP Framework 5.3 + .NET 6 + SQL Server场景的问题解答
问题1:如何确保更新Table-W-1时,关联的Table-R-1和Table-R-2记录不被修改?
可以从多个层面逐步强化控制,按严格程度排序:
代码层基础控制
- 为Table-R-1和Table-R-2实现只读仓储:自定义仓储时仅继承
IReadOnlyRepository<T>而非IRepository<T>,只暴露GetAsync、GetListAsync等查询方法,屏蔽所有修改类接口(UpdateAsync、DeleteAsync等)。 - 实体设为只读:将Table-R-1/Table-R-2实体的属性setter设为
private,仅通过构造函数初始化数据,从根源上避免代码中修改实体字段。 - 应用服务层限制:在处理Table-W-1更新的应用服务方法中,仅调用Table-R-1/Table-R-2的查询逻辑,绝不触发任何修改操作。
- 为Table-R-1和Table-R-2实现只读仓储:自定义仓储时仅继承
数据库权限兜底
给应用使用的SQL Server数据库账号,仅赋予Table-R-1和Table-R-2的SELECT权限,移除INSERT、UPDATE、DELETE权限。即便代码出现疏漏,数据库也会直接拒绝修改请求。ABP拦截器强制拦截
实现ABP方法拦截器,拦截所有针对Table-R-1/Table-R-2实体的修改方法,一旦检测到调用直接抛出业务异常:public class ReadOnlyEntityInterceptor : MethodInterceptorBase { protected override void Intercept(IAbpMethodInvocation invocation) { var targetEntityType = invocation.Method.GetParameters() .FirstOrDefault(p => typeof(IEntity).IsAssignableFrom(p.ParameterType))?.ParameterType; if (targetEntityType == typeof(TableR1) || targetEntityType == typeof(TableR2)) { throw new BusinessException("Table-R-1和Table-R-2为只读表,禁止修改"); } base.Intercept(invocation); } }在模块类中注册该拦截器,对仓储的修改方法生效即可。
问题2:同一作用域内读取Table-R-1/Table-R-2后更新Table-W-1,能否在前者被修改时触发并发异常阻止写入?
默认情况下不会触发并发异常,因为EF Core(ABP默认ORM)搭配SQL Server默认的「读已提交」隔离级别时,事务内读取的是数据快照,其他事务对Table-R-1/Table-R-2的修改不会被当前事务感知,当前事务仍会正常提交Table-W-1的更新。
要实现「前者被修改则阻止写入」的需求,可通过两种方案实现:
方案1:乐观并发控制(推荐)
- 给Table-R-1和Table-R-2添加
RowVersion列(SQL Server用timestamp类型,EF Core标记[Timestamp]属性):public class TableR1 : Entity<Guid> { // 其他业务属性 [Timestamp] public byte[] RowVersion { get; set; } } - 在处理Table-W-1更新的事务流程中:
- 读取Table-R-1/Table-R-2时,记录对应的
RowVersion值; - 提交Table-W-1更新前,重新查询目标记录的最新
RowVersion并与之前的值对比; - 若不一致,说明记录已被其他事务修改,抛出并发异常并回滚当前事务。
- 读取Table-R-1/Table-R-2时,记录对应的
方案2:悲观并发控制
- 设置事务隔离级别为可重复读:在ABP应用服务方法上标记
[UnitOfWork(IsolationLevel = IsolationLevel.RepeatableRead)],此级别保证事务内多次读取同一数据的结果一致,其他事务无法修改当前事务已读取的记录(会被阻塞直到当前事务完成)。 - 添加查询锁定提示:读取Table-R-1/Table-R-2时,使用
WITH (UPDLOCK, HOLDLOCK)强制对读取记录加更新锁,直到事务结束:
这种方式下,其他事务尝试修改锁定记录会被阻塞;若极端场景下记录被修改,当前事务提交时会触发并发异常。var targetR1 = await _dbContext.TableR1 .FromSqlRaw("SELECT * FROM [Table-R-1] WHERE Id = {0} WITH (UPDLOCK, HOLDLOCK)", targetId) .FirstOrDefaultAsync();
内容的提问来源于stack exchange,提问作者Anirban Paul
相关产品推荐
相关产品推荐

