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

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,这是问题核心:

  1. 实体状态错误:手动创建Car时指定了Guid主键,EF会默认将该实体标记为Modified状态,而非Added状态。因此SaveChanges时EF尝试更新一条不存在的记录,导致并发异常。
  2. 关联关系处理缺失:虽然将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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.18 13:47:32