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

升级.NET5后DbContext并发异常修复:代码及问题排查

问题:升级至.NET5后EF Core 3.1偶发删除并发异常

背景

原代码运行在.NET Framework 4.7.2 MVC + Entity Framework 6环境,已将业务逻辑、领域对象及数据层迁移至.NET Standard 2.0,数据层采用Entity Framework Core 3.1。近期将部分站点升级至.NET5,数据层未改动,升级后偶发以下异常:

Database operation expected to affect 1 row(s) but actually affected 0 row(s). Data may have been modified or deleted since entities were loaded.
at Microsoft.EntityFrameworkCore.Update.AffectedCountModificationCommandBatch.ThrowAggregateUpdaFHoncurrencyException(Int32 commandIndex, Int32 expectedRowsAffected, Int32 rowsAffected)
at Microsoft.EntityFrameworkCore.Update.AffectedCountModificationCommandBatch.ConsumeResultSetWithoutPropagation(Int32 commandIndex, RelationalDataReader reader)
at Microsoft.EntityFrameworkCore.Update.AffectedCountModificationCommandBatch.Consume(RelationalDataReader reader)
at Microsoft.EntityFrameworkCore.Update.ReaderModificationCommandBatch.Execute(IRelationalConnection connection)
at Microsoft.EntityFrameworkCore.Update.Internal.BatchExecutor.Execute(IEnumerable1 commandBatches, IRelationalConnection connection) at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.SaveChanges(IList1 entriesToSave)
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.SaveChanges(DbContext _, Boolean acceptAllChangesOnSuccess)
at Microsoft.EntityFrameworkCore.SqlServer.Storage.Internal.SqlServerExecutionStrategy.Execute[TState,TResult](TState state, Func3 operation, Func3 verifySucceeded)
at Microsoft.EntityFrameworkCore.DbContext.SaveChanges(Boolean acceptAllChangesOnSuccess)
at FH.Data.Models.EntityFramework.DataContext.FHDbContext.SaveChanges() in D:\Code\cntr\src\FH.Data.Models\Partials\DataContext\FHDbContext.cs:line 73
at FH.Data.Access.Repositories.Time.TimesheetRepository.SaveTimePunches(List1 timePunches) in D:\Code\cntr\src\FH.Data.Access\Repositories\Time\TimesheetRepository.cs:line 2096 at FH.BusinessLogic.Core.Timesheet.TimesheetController.SaveTimePunches(List1 timePunches) in D:\Code\cntr\src\FHBllStandard\Timesheet\TimesheetController.cs:line 892
at FH.Employees.Controllers.TimesheetController.SavePunches(List`1 timepunches) in D:\Code\cntr\src\FH.Employees\Controllers\TimesheetController.cs:line 139
at Microsoft.AspNetCore.Mvc.Infrastructure.ActionMethodExecutor.SyncActionResultExecutor.Execute(IActionResultTypeMapper mapper, ObjectMethodExecutor executor, Object controller, Object[] arguments)
at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.InvokeActionMethodAsync()
at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.InvokeNextActionFilterAsync()

相关代码

异常触发于SaveChanges()行:

var workDates = timePunches.Select(x => x.WorkDate.Date)
                           .Distinct().ToList();
var jobId = timePunches.First().JobId;

var punches = _context.TimePunches
                      .Where(y => workDates.Contains(y.WorkDate.Date) && y.JobId == jobId)
                      .ToList();

if (punches.Any())
{
    _context.TimePunches.RemoveRange(punches);
    _context.SaveChanges();
}

问题原因

  1. 并发概率提升:.NET5的请求处理效率更高,同一批TimePunch数据被多请求同时操作的概率增加。原代码先查询再删除的逻辑存在时间窗口:查询出数据后到执行删除前,其他请求可能已经删除了这批数据,导致当前SaveChanges()时实际影响行数为0,触发EF Core的并发验证。
  2. EF与EF Core行为差异:Entity Framework 6默认不会对“预期影响行数不符”抛出异常(除非配置了并发令牌),但EF Core 3.1默认会针对RemoveRange这类操作检查受影响行数,当实际行数与预期不符时抛出DbUpdateConcurrencyException,这是升级后异常显现的核心原因。

修复方案

方案1:直接执行批量删除(推荐)

跳过加载实体的步骤,直接在数据库层面执行删除,彻底消除并发时间窗口:

var workDates = timePunches.Select(x => x.WorkDate.Date)
                           .Distinct().ToList();
var jobId = timePunches.First().JobId;

// EF Core 3.1需引入Microsoft.EntityFrameworkCore.Relational包
var deleteCount = _context.TimePunches
                          .Where(y => workDates.Contains(y.WorkDate.Date) && y.JobId == jobId)
                          .ExecuteDelete();

若版本不支持ExecuteDelete,可直接执行原生SQL:

var jobIdParam = new SqlParameter("@JobId", jobId);
var workDateParams = workDates.Select((date, index) => new SqlParameter($"@WorkDate{index}", date)).ToArray();

var datePlaceholders = string.Join(", ", workDateParams.Select(p => p.ParameterName));
var sql = $"DELETE FROM TimePunches WHERE CAST(WorkDate AS DATE) IN ({datePlaceholders}) AND JobId = @JobId";

_context.Database.ExecuteSqlRaw(sql, workDateParams.Concat(new[] { jobIdParam }).ToArray());

方案2:捕获并处理并发异常

若需保留先查询再删除的逻辑,可捕获异常后重试或忽略:

var workDates = timePunches.Select(x => x.WorkDate.Date)
                           .Distinct().ToList();
var jobId = timePunches.First().JobId;

try
{
    var punches = _context.TimePunches
                          .Where(y => workDates.Contains(y.WorkDate.Date) && y.JobId == jobId)
                          .ToList();

    if (punches.Any())
    {
        _context.TimePunches.RemoveRange(punches);
        _context.SaveChanges();
    }
}
catch (DbUpdateConcurrencyException)
{
    // 重试一次,确认数据是否真的已被删除
    var retryPunches = _context.TimePunches
                               .Where(y => workDates.Contains(y.WorkDate.Date) && y.JobId == jobId)
                               .ToList();
    if (retryPunches.Any())
    {
        _context.TimePunches.RemoveRange(retryPunches);
        _context.SaveChanges();
    }
}

方案3:关闭EF Core并发检查(不推荐)

通过配置关闭受影响行数验证,但会隐藏真实并发问题,仅适合临时应急:
在FHDbContext的SaveChanges方法中修改:

public override int SaveChanges()
{
    return base.SaveChanges(SaveChangesOptions.IgnoreAllConcurrencyConflicts);
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.16 19:27:39