You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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()就会立即提交当前变更。

修改建议

  1. 移除数据访问层方法内的SaveChanges()调用:把所有数据访问层的增删改方法改成只操作实体(比如标记实体为Modified、Added状态),不执行SaveChanges(),把提交的控制权统一交给外层的事务逻辑。
  2. 在事务最后统一提交:在事务块内完成所有实体操作后,统一调用一次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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.19 22:53:11