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

EF Core第二次调用SaveChanges()时添加子实体到父集合失败问题

解决EF Core中父实体SaveChanges后添加子实体引发的并发异常问题

问题重现

在EF Core中配置父/子实体关系后,业务需要先添加父实体并调用SaveChanges(),后续再为该父实体添加子实体并再次执行SaveChanges()(属于复杂事务流程,必须拆分两次保存操作)。

但出现以下异常:

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.

该操作在旧版Entity Framework中可正常执行,且手动将子实体添加到对应DbSet时也能正常运行。

复现代码

internal class Program
{
    static void Main(string[] args)
    {
        // 清空之前的数据
        {
            using var db = new BloggingContext();
            var posts = db.Posts.ToList();
            var blogs = db.Blogs.ToList();

            foreach (var post in posts)
                db.Remove(post);

            foreach (var blog in blogs)
                db.Remove(blog);

            db.SaveChanges();
        }

        try
        {
            using var db = new BloggingContext();

            var blog = new Blog
            {
                Id = 1,
                Name = "Blog 1",
                SiteUri = "http://blogs.msdn.com/adonet",
                Posts = new List<Post>()
            };

            db.Add(blog);

            var post1 = new Post
            {
                Id = 11,
                BlogId = 1,
                Title = "Hello World",
                Content = "I wrote an app using EF Core!"
            };

            blog.Posts.Add(post1);

            db.SaveChanges();

            var post2 = new Post
            {
                Id = 12,
                BlogId = 1,
                Title = "Hello World 2",
                Content = "I wrote an app using EF Core 2!"
            };

            blog.Posts.Add(post2);
            // db.Posts.Add(post2); --> 启用此行可正常运行

            db.SaveChanges();

            Console.WriteLine("Done");
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.ToString());
        }
    }
}

public class Blog
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string SiteUri { get; set; }

    public virtual ICollection<Post> Posts { get; set; }
}

public class Post
{
    public int Id { get; set; }
    public string Title { get; set; }
    public string Content { get; set; }
    public DateTime PublishedOn { get; set; }
    public bool Archived { get; set; }

    public int BlogId { get; set; }
    public Blog Blog { get; set; }
}

public class BloggingContext : DbContext
{
    public DbSet<Blog> Blogs { get; set; }
    public DbSet<Post> Posts { get; set; }

    public BloggingContext()
    {
    }

    protected override void OnConfiguring(DbContextOptionsBuilder options)
        => options.UseSqlServer($"Server=.;Database=DbTests;Trusted_Connection=True;Encrypt=False");

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Blog>()
            .HasMany(e => e.Posts)
            .WithOne(e => e.Blog)
            .HasForeignKey(e => e.BlogId)
            .HasPrincipalKey(e => e.Id);
    }
}

数据库模型SQL

CREATE TABLE [Blogs] 
(
    [Id] int NOT NULL,
    [Name] nvarchar(max) NULL,
    [SiteUri] nvarchar(max) NULL,
    CONSTRAINT [PK_Blogs] PRIMARY KEY ([Id])
);

CREATE TABLE [Posts] 
(
    [Id] int NOT NULL,
    [Title] nvarchar(max) NULL,
    [Content] nvarchar(max) NULL,
    [PublishedOn] datetime2 NOT NULL,
    [Archived] bit NOT NULL,
    [BlogId] int NOT NULL,
    CONSTRAINT [PK_Posts] PRIMARY KEY ([Id]),
    CONSTRAINT [FK_Posts_Blogs_BlogId] FOREIGN KEY ([BlogId]) REFERENCES [Blogs] ([Id]) ON DELETE CASCADE
);

问题原因

EF Core与旧版EF的变更跟踪逻辑存在差异:第一次SaveChanges()后,父实体已持久化到数据库,此时通过导航属性添加新子实体时,EF Core会错误地尝试更新父实体(而非仅插入子实体)。但父实体在数据库中未发生任何修改,导致乐观并发检查失败,触发异常。

通用修复方案

方案1:关闭父实体的并发检查

如果业务不需要对父实体做乐观并发控制,可以在模型配置中关闭:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .HasMany(e => e.Posts)
        .WithOne(e => e.Blog)
        .HasForeignKey(e => e.BlogId)
        .HasPrincipalKey(e => e.Id);
    
    // 关闭Blog实体的并发令牌检查
    modelBuilder.Entity<Blog>().Property(b => b.Id).IsConcurrencyToken(false);
}

若需全局关闭并发警告,可在上下文配置中设置:

protected override void OnConfiguring(DbContextOptionsBuilder options)
    => options.UseSqlServer("你的连接字符串")
        .ConfigureWarnings(warnings => warnings.Ignore(CoreEventId.DbUpdateConcurrencyWarning));

方案2:显式设置子实体状态

添加子实体后,手动将其状态标记为Added,避免EF Core误触发父实体更新:

blog.Posts.Add(post2);
// 手动指定子实体为新增状态
db.Entry(post2).State = EntityState.Added;
db.SaveChanges();

方案3:直接添加子实体到DbSet(已验证有效)

跳过导航属性,直接将子实体添加到对应的DbSet,EF Core会正确识别仅需插入子实体:

// blog.Posts.Add(post2);
db.Posts.Add(post2);
db.SaveChanges();

方案4:重新加载父实体

第一次SaveChanges()后重新加载父实体,确保上下文跟踪的状态与数据库一致:

db.SaveChanges();
// 重新加载父实体
db.Entry(blog).Reload();

var post2 = new Post { ... };
blog.Posts.Add(post2);
db.SaveChanges();

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.25 22:30:58