Entity Framework Core多父类共享子实体后子实体丢失问题
EF Core中共享子实体导致父类丢失子项的原因分析
问题场景
在EF Core的简单模型中,父类包含子实体列表。当向数据库添加两个共享同一个子实体的TestDataWithListOfChildren实例后,其中一个父类会丢失该共享子实体。
示例代码
创建并保存数据:
// 创建数据库 using TestDb testDb = new(); // 定义数据 var sharedChild = new TestSubData("Shared", 1); var entry1 = new TestDataWithListOfChildren("Max1", new List<TestSubData> { sharedChild, new TestSubData("unique #1",2) }); var entry2 = new TestDataWithListOfChildren("Max2", new List<TestSubData> { sharedChild, new TestSubData("unique #2",3) }); testDb.Add(entry1); await testDb.SaveChangesAsync(); // 验证初始数据正常 var data1 = testDb.TestDataWithListOfChildren .Include(x => x.Children).ToArray(); Assert.That(data1[0].Children.Count, Is.EqualTo(2)); testDb.Add(entry2); await testDb.SaveChangesAsync();
读取数据并断言(出现异常):
Assert.That(testDb.TestDataWithListOfChildren.Count(), Is.EqualTo(2)); Assert.That(testDb.TestSubData.Count(), Is.EqualTo(3)); // 断言失败:第一个父实体的子集合数量变为1,共享子实体丢失 Assert.That(data1[0].Children.Count, Is.EqualTo(2)); var data2 = testDb.TestDataWithListOfChildren .Include(x => x.Children).ToArray(); // 同样断言失败 Assert.That(data2[0].Children.Count, Is.EqualTo(2)); Assert.That(data2[1].Children.Count, Is.EqualTo(2));
数据模型
record TestDataWithListOfChildren(string Name, IList<TestSubData> Children, long Id = default) { public TestDataWithListOfChildren() : this(default, default) { } }; record TestSubData(string Name, int Value, long Id = default); class TestDb : DbContext { public DbSet<TestDataWithListOfChildren> TestDataWithListOfChildren { get; set; } = default!; public DbSet<TestSubData> TestSubData { get; set; } = default!; public TestDb() : base(new DbContextOptionsBuilder().UseInMemoryDatabase("testDb", new InMemoryDatabaseRoot()).Options) { } }
原因解析
核心原因是EF Core默认将集合关联视为一对多关系:
- 第一次保存
entry1时,sharedChild会被EF Core标记为属于entry1,EF Core会自动为TestSubData生成指向TestDataWithListOfChildren的隐式外键,记录其所属的父实体。 - 添加
entry2并保存时,EF Core发现sharedChild已被上下文追踪且关联到entry1,但又被加入entry2的子集合,此时会判定为关联关系变更,自动更新sharedChild的外键指向entry2,同时从entry1的子集合中移除该实体,以此维护一对多关系的约束(一个子实体只能属于一个父实体)。
独立子属性可正常工作的原因
当使用独立子属性(如TestDataWithTwoChildren中的Child1、Child2)时,EF Core不会将其视为一对多关系,而是将每个属性视为独立引用。这种情况下,多个父实体可以持有同一个子实体的引用,EF Core不会修改这些关联,因此不会出现子实体丢失的问题。
解决方案:配置多对多关系
如果需要实现一个子实体属于多个父实体的场景,需显式配置多对多关系:
protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity<TestDataWithListOfChildren>() .HasMany(t => t.Children) .WithMany() // 子实体无需反向导航属性 .UsingEntity(j => j.ToTable("TestDataTestSubData")); // 指定中间连接表名 }
配置后,EF Core会创建中间连接表记录父实体与子实体的多对多关联,同一个子实体可被多个父实体引用,不会出现子实体从某个父集合中丢失的情况。
内容的提问来源于stack exchange,提问作者LewxX
相关产品推荐
相关产品推荐

