EF Core事务上下文执行SaveChanges后无法回滚问题求助
问题:EF Core事务无法回滚,变更已提交到数据库
我在Blazor Server应用中编写业务逻辑,使用EF Core 7和Pomelo提供程序,DbContext注册为Scoped服务。在服务方法中调用多个数据访问层方法,每个数据访问方法内都会调用dbcontext.SaveChanges()。我期望代码出错时能回滚所有数据库变更,但实际变更已提交至数据库,请问我忽略了什么?
相关代码:
public async Task<RevisionDTO> UpdateRevisionByRevisionDTO(RevisionDTO revisionDTO) { var document = documentsRepository.GetDocumentByDocNumber(revisionDTO.DocumentNumber); var revisions = documentDetailsRepository.GetDocumentdetailsByrfDocuments(document.rfDocuments.GetValueOrDefault()); var revision = revisions.FirstOrDefault(r => r.RevisionNo == revisionDTO.RevisionNumber); Documentdetails updatedRevision = new Documentdetails() { rfDocumentDetails = revision.rfDocumentDetails, rfDocuments = revision.rfDocuments, RevisionNo = revisionDTO.RevisionNumber, DateofIssue = revisionDTO.DateOfIssue, Description = revisionDTO.Description, AmendedBy = revisionDTO.AmendedBy, AuthorisedBy = revisionDTO.AuthorizedBy, AuthorisedDate = revisionDTO.AuthorizedDate, }; using (var transaction = context.Database.BeginTransaction()) { try { documentDetailsRepository.UpdateDocumentDetails(updatedRevision, revision.rfDocumentDetails.GetValueOrDefault()); await _logService.AddLog(LogsUtils.ActionType.Edit, document.DrawingNo, revisionDTO.RevisionNumber, "", LogsUtils.Category.Revision); historyTextService.UpdateHistoryText(revisionDTO.DocumentNumber); throw new Exception(); transaction.Commit(); return RevisionTransformer.ToDTO(updatedRevision); } catch (Exception ex) { transaction.Rollback(); throw new Exception("Revision update failed.", ex); } } }
问题原因及解决方法
核心问题
- 数据访问层的
SaveChanges()会直接提交变更:你在事务块里调用的UpdateDocumentDetails、AddLog、UpdateHistoryText这些方法内部都执行了SaveChanges(),这个方法默认会在没有显式事务的情况下,自动创建并提交一个隐式事务。外层的显式事务根本无法管控这些已经被单独提交的变更,所以即使后续触发回滚,之前已经提交的变更也不会被撤销。 - Scoped的DbContext注册不影响这个问题,不管生命周期如何,只要调用
SaveChanges()就会立即提交当前变更。
修改建议
- 移除数据访问层方法内的
SaveChanges()调用:把所有数据访问层的增删改方法改成只操作实体(比如标记实体为Modified、Added状态),不执行SaveChanges(),把提交的控制权统一交给外层的事务逻辑。 - 在事务最后统一提交:在事务块内完成所有实体操作后,统一调用一次
context.SaveChangesAsync(),再执行transaction.Commit()。
修改后的示例代码:
public async Task<RevisionDTO> UpdateRevisionByRevisionDTO(RevisionDTO revisionDTO) { var document = documentsRepository.GetDocumentByDocNumber(revisionDTO.DocumentNumber); var revisions = documentDetailsRepository.GetDocumentdetailsByrfDocuments(document.rfDocuments.GetValueOrDefault()); var revision = revisions.FirstOrDefault(r => r.RevisionNo == revisionDTO.RevisionNumber); Documentdetails updatedRevision = new Documentdetails() { rfDocumentDetails = revision.rfDocumentDetails, rfDocuments = revision.rfDocuments, RevisionNo = revisionDTO.RevisionNumber, DateofIssue = revisionDTO.DateOfIssue, Description = revisionDTO.Description, AmendedBy = revisionDTO.AmendedBy, AuthorisedBy = revisionDTO.AuthorizedBy, AuthorisedDate = revisionDTO.AuthorizedDate, }; using (var transaction = context.Database.BeginTransaction()) { try { // 数据访问层仅处理实体状态,不调用SaveChanges documentDetailsRepository.UpdateDocumentDetails(updatedRevision, revision.rfDocumentDetails.GetValueOrDefault()); await _logService.AddLog(LogsUtils.ActionType.Edit, document.DrawingNo, revisionDTO.RevisionNumber, "", LogsUtils.Category.Revision); historyTextService.UpdateHistoryText(revisionDTO.DocumentNumber); // 统一提交所有变更到数据库 await context.SaveChangesAsync(); // 提交事务 transaction.Commit(); return RevisionTransformer.ToDTO(updatedRevision); } catch (Exception ex) { transaction.Rollback(); throw new Exception("Revision update failed.", ex); } } }
如果数据访问层必须保留SaveChanges()的调用,可以考虑给方法增加一个bool saveChanges = true的参数,在调用时传入false来跳过提交,等所有操作完成后统一提交,但第一种方案更简洁可控。
内容的提问来源于stack exchange,提问作者Lash
相关产品推荐
相关产品推荐

