EF Core 6.0 自引用实体操作触发循环依赖异常问题咨询
我认为这可能是EF Core 6.0的Bug,但在向EF Core GitHub仓库提交Bug前,想先在此咨询确认。
我无法完全确定这是Bug,至少它属于不符合预期的行为。
该行为是我迁移到.NET 6后首次出现的,我已经通过下方3个文件组成的简单项目复现了该问题。最初我以为这是一项破坏性变更,所以查阅了所有相关变更文档,但未找到任何会触发该错误的破坏性变更说明。由于无法将错误与已知变更关联,我开始修改.csproj文件中的EF Core版本,当我将所有EF Core相关包版本切换为6.0.0-rc.2.21480.5后,错误就不再出现了。
另外需要注意的是,如果我在Program.cs的删除操作后添加一次SaveChanges调用,错误也不会出现。
请问各位认为这是EF Core 6.0的Bug,还是我遗漏了某项新特性或破坏性变更?
Person.cs
#nullable disable public class Person { public int PersonId { get; set; } public string Name { get; set; } public int? ParentId { get; set; } public Person Parent { get; set; } protected Person() { } public Person(string name, Person parent) { this.Name = name; if (parent is not null) { this.Parent = parent; } } }
Context.cs
#nullable disable using Microsoft.EntityFrameworkCore; public class Context : DbContext { public Context() {} public DbSet<Person> Parents { get; set; } protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { optionsBuilder.UseSqlServer( "Data Source=DES04;Initial Catalog=Test;Integrated Security=true"); base.OnConfiguring(optionsBuilder); } protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity<Person>() .HasOne(p => p.Parent) .WithMany() .IsRequired(false) .OnDelete(DeleteBehavior.Restrict); modelBuilder.Entity<Person>() .HasKey(p => p.PersonId); } }
Program.cs
var context = new Context(); var parent = new Person("1", null); var child1 = new Person("2", parent); var child2 = new Person("3", parent); var grandchild1 = new Person("4", child1); var grandchild2 = new Person("5", child1); var grandchild3 = new Person("6", child2); var grandchild4 = new Person("7", child2); context.Add(parent); context.Add(child1); context.Add(child2); context.Add(grandchild1); context.Add(grandchild2); context.Add(grandchild3); context.Add(grandchild4); context.SaveChanges(); context.Remove(parent); context.Remove(child1); context.Remove(child2); context.Remove(grandchild1); context.Remove(grandchild2); context.Remove(grandchild3); context.Remove(grandchild4); parent = new Person("1", null); child1 = new Person("2", parent); child2 = new Person("3", parent); grandchild1 = new Person("4", child1); grandchild2 = new Person("5", child1); grandchild3 = new Person("6", child2); grandchild4 = new Person("7", child2); context.Add(parent); context.Add(child1); context.Add(child2); context.Add(grandchild1); context.Add(grandchild2); context.Add(grandchild3); context.Add(grandchild4); context.SaveChanges();
堆栈跟踪
未处理的异常。System.InvalidOperationException: 无法保存更改,因为在待保存数据中检测到循环依赖:'Person [Deleted]Person [Deleted] Parent { 'ParentId' } <-
Person [Deleted] Parent { 'ParentId' } <-
Person [Deleted]如需查看更多信息,请调用'DbContextOptionsBuilder.EnableSensitiveDataLogging'。at Microsoft.EntityFrameworkCore.Utilities.Multigraph
2.ThrowCycle(List1 cycle, Func2 formatCycle, Func2 formatException)
at Microsoft.EntityFrameworkCore.Utilities.Multigraph2.BatchingTopologicalSort(Func4 tryBreakEdge, Func2 formatCycle) at Microsoft.EntityFrameworkCore.Update.Internal.CommandBatchPreparer.TopologicalSort(IEnumerable1 commands)
at Microsoft.EntityFrameworkCore.Update.Internal.CommandBatchPreparer.BatchCommands(IList1 entries, IUpdateAdapter updateAdapter)+MoveNext() at Microsoft.EntityFrameworkCore.Update.Internal.BatchExecutor.Execute(IEnumerable1 commandBatches, IRelationalConnection connection)
at Microsoft.EntityFrameworkCore.Storage.RelationalDatabase.SaveChanges(IList1 entries) at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.SaveChanges(IList1 entriesToSave)
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.SaveChanges(StateManager stateManager, Boolean acceptAllChangesOnSuccess)
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.<>c.b__104_0(DbContext _, ValueTuple2 t) at Microsoft.EntityFrameworkCore.SqlServer.Storage.Internal.SqlServerExecutionStrategy.Execute[TState,TResult](TState state, Func3 operation, Func`3 verifySucceeded)
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.SaveChanges(Boolean acceptAllChangesOnSuccess)
at Microsoft.EntityFrameworkCore.DbContext.SaveChanges(Boolean acceptAllChangesOnSuccess)
at Microsoft.EntityFrameworkCore.DbContext.SaveChanges()
at Program.$(String[] args) in C:\Users\Dev04\Desktop\Nueva carpeta\test\Program.cs:line 44
版本信息
- EF Core:
Microsoft.EntityFrameworkCore6.0.0 - 数据库提供程序:
Microsoft.EntityFrameworkCore.SqlServer6.0.0 - 目标框架: .NET 6.0.0
这是EF Core 6.0.0正式版的已知Bug,并非你使用方式有误或者遗漏了破坏性变更。
根因说明
EF Core 6.0.0正式版在优化变更集的拓扑排序逻辑时,对自引用实体的同批次删除+新增场景处理存在缺陷:当你在同一个SaveChanges批次中,同时提交存在关联的已删除自引用实体、以及新的同名关联自引用实体时,排序逻辑会错误识别出不存在的循环依赖,抛出对应的异常。
你观测到的两个现象也完全匹配这个根因:
- 切换到6.0.0-rc.2版本不会复现,是因为这个有问题的排序优化是在rc2版本之后才合并到正式版的
- 删除操作后加一次
SaveChanges就正常,是因为删除和新增操作分两个批次提交,同批次变更集不存在冲突的实体关联关系
解决办法
- 优先方案:升级EF Core版本
微软已经在EF Core 6.0.1及之后的补丁版本中修复了这个Bug,你只需要将项目中所有EF Core相关包的版本升级到6.0.1或更高,不需要修改现有业务代码就能解决问题。 - 临时兼容方案
如果你暂时无法升级EF Core版本,可以选择以下任意一种方式规避:
- 删除实体前先手动清空所有实体的
Parent导航属性引用,再执行Remove操作 - 按照你测试的逻辑,删除操作完成后单独调用一次
SaveChanges,再执行后续新增逻辑
内容的提问来源于stack exchange,提问作者josecu08

