使用System.Transactions在EF中批量更新多行报错,求解决方案
解决EF Core中SqlServerRetryingExecutionStrategy不支持手动事务的问题
这个错误的核心原因是:EF Core为SQL Server默认配置的SqlServerRetryingExecutionStrategy(重试执行策略)需要完全掌控事务的生命周期——它设计用来自动处理SQL Server的可重试错误(比如死锁、临时连接失败),但你手动调用BeginTransaction()创建的事务不在它的管理范围内,所以会触发这个报错。
正确的解决方案:用执行策略包裹整个事务逻辑
我们需要使用EF提供的CreateExecutionStrategy()方法获取重试策略,然后把所有需要在事务中执行的操作放到这个策略的Execute方法里,让它来管理整个事务的重试和提交/回滚逻辑。
修改后的代码如下:
public void Update(List<PortfolioCompanyLinkModel> records) { // 获取SQL Server的重试执行策略 var executionStrategy = _context.Database.CreateExecutionStrategy(); // 让执行策略包裹整个事务操作,确保可重试 executionStrategy.Execute(() => { using (var transaction = _context.Database.BeginTransaction()) { try { foreach (var item in records) { var portfolioCompanyLink = _context.PortfolioCompanyLink.FirstOrDefault(p => p.Id == item.Id); // 增加null判断,避免空引用异常 if (portfolioCompanyLink == null) { continue; // 或者根据业务逻辑抛出异常 } portfolioCompanyLink.ModifiedBy = _loggedInUser; portfolioCompanyLink.ModifiedOn = DateTime.UtcNow; portfolioCompanyLink.URL = item.URL; } // 统一提交所有更改,比循环内多次SaveChanges更高效 _context.SaveChanges(); transaction.Commit(); } catch (Exception ex) { transaction.Rollback(); // 重新抛出异常,让执行策略判断是否需要重试(比如死锁这类可重试错误) throw; } } }); }
关键细节说明:
- 重试策略的作用:
Execute方法会将内部的所有逻辑视为一个原子单元,如果遇到SQL Server的可重试错误,它会自动回滚当前事务,然后重新执行整个逻辑,直到成功或达到重试上限。 - 优化SaveChanges调用:原来的代码在循环内每次调用
SaveChanges会产生多次数据库请求,改成最后统一调用一次,大幅提升性能,同时确保所有更改在同一个事务中提交。 - 空引用防护:
FirstOrDefault可能返回null,必须增加判断,避免后续赋值时抛出空引用异常。 - 异常处理:捕获异常回滚事务后,一定要重新抛出异常,这样执行策略才能识别是否是可重试的错误,进而决定是否重试。
如果你确实不需要重试策略(不推荐,因为SQL Server的死锁等问题很常见),也可以在DbContext配置中禁用它,但这是下策:
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { optionsBuilder .UseSqlServer("YourConnectionString", builder => builder.ExecutionStrategy(dependencies => new DefaultExecutionStrategy(dependencies))); }
不过强烈建议保留重试策略,用第一种方法解决问题,这样你的代码能更好地应对SQL Server的临时故障。
内容的提问来源于stack exchange,提问作者Harsha Mullangi
相关产品推荐
相关产品推荐

