ASP.NET Core添加关联实体触发DbUpdateConcurrencyException问题排查
问题:ASP.NET Core中EF Core保存关联实体时触发DbUpdateConcurrencyException
我正在开发一个基于ASP.NET Core的管理系统,在将关联实体保存到数据库时遇到问题。定义了User和Car两个实体类,编写端点创建Car实例并添加到User的Cars集合,保存更改时触发以下异常:
Microsoft.EntityFrameworkCore.DbUpdateConcurrencyException: 数据库操作预期影响1行,但实际影响0行;实体加载后数据可能已被修改或删除。
对比微软官方文档代码,逻辑一致,但问题依旧,求排查。
模型代码
public class User : BaseEntity { public string FirstName { get; protected set; } public string LastName { get; protected set; } public ICollection<Car> Cars { get; set; } public User(){} public User(Guid id, string firstName, string lastName) { Id = id; FirstName = firstName; LastName = lastName; } public void AddCar(Car car) { Cars.Add(car); } }
public class Car : BaseEntity { public string Model { get; protected set; } public DateOnly Year { get; protected set; } public Guid UserId { get; set; } public User User { get; protected set; } public Car() {} public Car(Guid id, string model, DateOnly year) { Id = id; Model = model; Year = year; } }
上下文代码
public class AppDbContext : DbContext { public AppDbContext() { } public AppDbContext(DbContextOptions<AppDbContext> options) : base(options) { } public DbSet<User> Users { get; set; } public DbSet<Car> Cars { get; set; } protected override void OnModelCreating(ModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder); modelBuilder.HasDefaultSchema("test"); modelBuilder.Entity<User>(entity => { entity.HasKey(x => x.Id); entity.HasMany(u => u.Cars) .WithOne(c => c.User) .HasForeignKey(c => c.UserId); }); modelBuilder.Entity<Car>(entity => { entity.HasKey(c => c.Id); entity.HasOne(c => c.User) .WithMany(u => u.Cars) .HasForeignKey(c => c.UserId); }); modelBuilder.ApplyConfigurationsFromAssembly(Assembly.GetExecutingAssembly()); } }
端点代码
[HttpPost("{userId}/add-car")] public async Task<IActionResult> AddCar(Guid userId, [FromBody] AddCarRequest request) { await using (var context = new AppDbContext()) { var car = new Car(Guid.NewGuid(), request.Model, request.Year); var user = context.Users.Include(x => x.Cars).FirstOrDefault(x => x.Id == userId); user.AddCar(car); await context.SaveChangesAsync(); return Ok(); } }
异常信息
Microsoft.EntityFrameworkCore.DbUpdateConcurrencyException: The database operation was expected to affect 1 row(s), but actually affected 0 row(s); data may have been modified or deleted since entities were loaded. See https://go.microsoft.com/fwlink/?LinkId=527962 for information on understanding and handling optimistic concurrency exceptions. at Npgsql.EntityFrameworkCore.PostgreSQL.Update.Internal.NpgsqlModificationCommandBatch.ThrowAggregateUpdateConcurrencyExceptionAsync(RelationalDataReader reader, Int32 commandIndex, Int32 expectedRowsAffected, Int32 rowsAffected, CancellationToken cancellationToken) at Npgsql.EntityFrameworkCore.PostgreSQL.Update.Internal.NpgsqlModificationCommandBatch.Consume(RelationalDataReader reader, Boolean async, CancellationToken cancellationToken) at Microsoft.EntityFrameworkCore.Update.ReaderModificationCommandBatch.ExecuteAsync(IRelationalConnection connection, CancellationToken cancellationToken) at Microsoft.EntityFrameworkCore.Update.ReaderModificationCommandBatch.ExecuteAsync(IRelationalConnection connection, CancellationToken cancellationToken) at Microsoft.EntityFrameworkCore.Update.Internal.BatchExecutor.ExecuteAsync(IEnumerable`1 commandBatches, IRelationalConnection connection, CancellationToken cancellationToken) at Microsoft.EntityFrameworkCore.Update.Internal.BatchExecutor.ExecuteAsync(IEnumerable`1 commandBatches, IRelationalConnection connection, CancellationToken cancellationToken) at Microsoft.EntityFrameworkCore.Update.Internal.BatchExecutor.ExecuteAsync(IEnumerable`1 commandBatches, IRelationalConnection connection, CancellationToken cancellationToken) at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.SaveChangesAsync(IList`1 entriesToSave, CancellationToken cancellationToken) at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.SaveChangesAsync(StateManager stateManager, Boolean acceptAllChangesOnSuccess, CancellationToken cancellationToken) at Npgsql.EntityFrameworkCore.PostgreSQL.Storage.Internal.NpgsqlExecutionStrategy.ExecuteAsync[TState,TResult](TState state, Func`4 operation, Func`4 verifySucceeded, CancellationToken cancellationToken) at Microsoft.EntityFrameworkCore.DbContext.SaveChangesAsync(Boolean acceptAllChangesOnSuccess, CancellationToken cancellationToken) at EFTest.Controllers.DefaultController.AddCar(Guid userId, AddCarRequest request) in S:\Repos\EFTest\EFTest\Controllers\DefaultController.cs:line 38 at EFTest.Controllers.DefaultController.AddCar(Guid userId, AddCarRequest request) in S:\Repos\EFTest\EFTest\Controllers\DefaultController.cs:line 40 at Microsoft.AspNetCore.Mvc.Infrastructure.ActionMethodExecutor.TaskOfIActionResultExecutor.Execute(ActionContext actionContext, IActionResultTypeMapper mapper, ObjectMethodExecutor executor, Object controller, Object[] arguments) at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeActionMethodAsync>g__Logged|12_1(ControllerActionInvoker invoker) at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeNextActionFilterAsync>g__Awaited|10_0(ControllerActionInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted) at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Rethrow(ActionExecutedContextSealed context) at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted) at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeInnerFilterAsync>g__Awaited|13_0(ControllerActionInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted) at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeFilterPipelineAsync>g__Awaited|20_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted) at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Logged|17_1(ResourceInvoker invoker) at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Logged|17_1(ResourceInvoker invoker) at Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context) at Swashbuckle.AspNetCore.SwaggerUI.SwaggerUIMiddleware.Invoke(HttpContext httpContext) at Swashbuckle.AspNetCore.Swagger.SwaggerMiddleware.Invoke(HttpContext httpContext, ISwaggerProvider swaggerProvider) at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context)
官方示例对比
using (var context = new BloggingContext()) { var blog = context.Blogs.Include(b => b.Posts).First(); var post = new Post { Title = "Intro to EF Core" }; blog.Posts.Add(post); context.SaveChanges(); }
EF执行的查询语句
info: 13.09.2024 20:51:02.616 RelationalEventId.CommandExecuted[20101] (Microsoft.EntityFrameworkCore.Database.Command) Executed DbCommand (20ms) [Parameters=[@__userId_0='42d2ac6e-e663-41a9-830e-3e49b38634c9'], CommandType='Text', CommandTimeout='30'] SELECT t."Id", t."CreatedAt", t."FirstName", t."LastName", t."UpdatedAt", c."Id", c."CreatedAt", c."Model", c."UpdatedAt", c."UserId", c."Year" FROM ( SELECT u."Id", u."CreatedAt", u."FirstName", u."LastName", u."UpdatedAt" FROM test."Users" AS u WHERE u."Id" = @__userId_0 LIMIT 1 ) AS t LEFT JOIN test."Cars" AS c ON t."Id" = c."UserId" ORDER BY t."Id" info: 13.09.2024 20:51:03.025 RelationalEventId.CommandExecuted[20101] (Microsoft.EntityFrameworkCore.Database.Command) Executed DbCommand (23ms) [Parameters=[@p5='7c87ad78-534b-4e1c-a472-653eab8a0fb9', @p0='0001-01-01T00:00:00.0000000' (DbType = DateTime), @p1='TEST' (Nullable = false), @p2='0001-01-01T00:00:00.0000000' (DbType = DateTime), @p3='42d2ac6e-e663-41a9-830e-3e49b38634c9', @p4='01/01/2000' (DbType = Date)], CommandType='Text', CommandTimeout='30'] UPDATE test."Cars" SET "CreatedAt" = @p0, "Model" = @p1, "UpdatedAt" = @p2, "UserId" = @p3, "Year" = @p4 WHERE "Id" = @p5;
问题原因与解决方案
从EF执行的SQL可以看出,当前代码在执行UPDATE操作而非INSERT操作来添加Car,这是问题核心:
- 实体状态错误:手动创建Car时指定了Guid主键,EF会默认将该实体标记为
Modified状态,而非Added状态。因此SaveChanges时EF尝试更新一条不存在的记录,导致并发异常。 - 关联关系处理缺失:虽然将Car添加到User的Cars集合,但EF未正确识别外键关联,加上实体状态错误,最终触发更新而非插入。
修复方案:
方案1:让EF自动生成主键(推荐)
修改Car的构造函数,不手动指定Id,让EF通过数据库生成(如PostgreSQL的uuid_generate_v4()):
public Car(string model, DateOnly year) { Model = model; Year = year; }
同时在上下文配置中确保主键为数据库生成:
modelBuilder.Entity<Car>(entity => { entity.HasKey(c => c.Id); entity.Property(c => c.Id).HasDefaultValueSql("uuid_generate_v4()"); // PostgreSQL示例 // 其他配置... });
方案2:手动将Car标记为Added状态
如果必须手动指定Id,在添加到集合后显式设置实体状态:
var car = new Car(Guid.NewGuid(), request.Model, request.Year); var user = context.Users.Include(x => x.Cars).FirstOrDefault(x => x.Id == userId); user.AddCar(car); context.Entry(car).State = EntityState.Added; // 显式标记为新增 await context.SaveChangesAsync();
方案3:简化关联添加逻辑
直接设置Car的UserId并添加到DbSet,无需通过User集合:
var car = new Car(Guid.NewGuid(), request.Model, request.Year); car.UserId = userId; context.Cars.Add(car); await context.SaveChangesAsync();
额外优化点:
- 移除OnModelCreating中重复的关联配置(User和Car的HasMany/HasOne只需配置一端即可)
- 检查BaseEntity是否包含并发令牌(如RowVersion),如果有需确保正确配置,避免误触发并发异常
- 使用FirstOrDefault时需处理user为null的情况,避免空引用异常
内容的提问来源于stack exchange,提问作者ZML
相关产品推荐
相关产品推荐

