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

.NET Framework 4.6.2下EF回滚后仍持续抛重复主键错误求助

问题描述

在.NET Framework 4.6.2环境下,使用Entity Framework结合事务向SQL Server插入数据的方法,代码如下:

public async Task InsertReportRecord(Guid consultantReportId, string code, string studyId, string serverLocation)
{
    using (var transaction = _reportRepository.BeginTransaction(IsolationLevel.RepeatableRead))
    {
        try
        {
            var record = new Report()
            {
                ConsultantReportId = consultantReportId,
                Code = code,
                StudyId = studyId,
                RequestedOnUtc = DateTime.UtcNow,
                ServerLocation = serverLocation
            };

            _reportRepository.Add(record);
            await _reportRepository.SaveAsync();
            transaction.Commit();
        }
        catch (Exception e)
        {
            _logger.Error("DB Transaction error, rolling back: " + e);
            transaction.Rollback();
        }
    }
}

出现的异常情况:

  • 插入ID为123的重复行时,抛出重复主键错误(符合预期);
  • 后续插入完全不同的ID为456的行时,仍抛出ID=123的重复主键错误(不符合预期);
  • 该问题会持续数小时,直到错误切换到其他ID。

已知需从根源避免插入重复数据,但希望代码能在出错后不影响后续操作。


问题分析与解决方案

核心原因

Entity Framework的DbContext是有状态的:当你将实体添加到上下文的变更跟踪器后,即使事务回滚,这个实体依然会留在跟踪器中。如果你的_reportRepository对应的DbContext是长生命周期实例(比如单例),后续调用SaveAsync时,EF会尝试再次提交这个已标记为"待插入"的错误实体,导致重复报错。

解决办法

1. 使用短生命周期的DbContext(最优方案)

确保仓库依赖的DbContext是短生命周期的(比如在ASP.NET中配置为Scoped,每个请求实例化一次)。这样每次操作结束后上下文会被销毁,不会遗留之前的错误实体,从根源避免状态污染。

调整代码示例(直接使用DbContext而非封装的仓库):

public async Task InsertReportRecord(Guid consultantReportId, string code, string studyId, string serverLocation)
{
    // 每次操作创建新的DbContext实例,用完即销毁
    using (var context = new YourDbContext())
    using (var transaction = context.Database.BeginTransaction(IsolationLevel.RepeatableRead))
    {
        try
        {
            var record = new Report()
            {
                ConsultantReportId = consultantReportId,
                Code = code,
                StudyId = studyId,
                RequestedOnUtc = DateTime.UtcNow,
                ServerLocation = serverLocation
            };

            context.Reports.Add(record);
            await context.SaveChangesAsync();
            transaction.Commit();
        }
        catch (Exception e)
        {
            _logger.Error("DB Transaction error, rolling back: " + e);
            transaction.Rollback();
        }
    }
}

2. 回滚后清理变更跟踪器(兼容现有仓库结构)

如果无法修改仓库的生命周期,在catch块中手动清空上下文的变更跟踪器,移除所有待提交的实体:

public async Task InsertReportRecord(Guid consultantReportId, string code, string studyId, string serverLocation)
{
    using (var transaction = _reportRepository.BeginTransaction(IsolationLevel.RepeatableRead))
    {
        try
        {
            var record = new Report()
            {
                ConsultantReportId = consultantReportId,
                Code = code,
                StudyId = studyId,
                RequestedOnUtc = DateTime.UtcNow,
                ServerLocation = serverLocation
            };

            _reportRepository.Add(record);
            await _reportRepository.SaveAsync();
            transaction.Commit();
        }
        catch (Exception e)
        {
            _logger.Error("DB Transaction error, rolling back: " + e);
            transaction.Rollback();
            // 清理变更跟踪器中的所有实体,避免后续提交重复错误
            _reportRepository.Context.ChangeTracker.Clear();
            // 注:需要仓库暴露DbContext对象,或提供专门的清理方法
        }
    }
}

3. 禁用变更跟踪(仅适用于批量插入场景)

如果该方法仅做插入操作,可临时禁用变更跟踪,避免实体被缓存:

context.Reports.Add(record);
context.Entry(record).State = EntityState.Added; // 显式设置状态,跳过变更跟踪缓存

内容的提问来源于stack exchange,提问作者Reddy

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.15 07:28:16