.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
相关产品推荐
相关产品推荐

