EF Core:当 DB-first FK 是错误的方式时如何级联删除?
EF Core: How to cascade delete when DB-first FK is the wrong way around?
考虑遗留数据库的这个(诚然相当混乱)实体模型:
如您所见,它对 Blog
建模,其中包含 Entry
的集合,每个指向一个博客 Post
。箭头的尖端指向关系的principal/parent。至少这是 EF 核心根据存储外键的位置看到它的方式。
不幸的是,Entry
和 Post
之间的 FK 是错误的方式,因为 Post
实际上是 Entry
的 child;即当我从 Blog
中删除 Entry
时,关联的 Post
也应该被删除。
由于我无法更改遗留数据库的模式,我想知道如何说服 EF 到 cascade-delete Post
s,尽管它不认为它们是 children Entry
s.
我尝试明确指定 DeleteBehavior
但无济于事:
modelBuilder.Entity<Entry>()
.HasOne(e => e.Post)
.WithOne(p => p.Entry)
.OnDelete(DeleteBehavior.Cascade);
我还尝试使导航 属性 Post.Entry
成为必需,以指示 Post
不能没有 Entry
,但这也无济于事:
public class Post {
// ...
[Required]
[InverseProperty(nameof(Entry.Post))]
public Entry Entry { get; set; }
}
我目前正在检查 owned entity types 是否有帮助,但我持怀疑态度。
我想我需要的是一种明确说明实体关系的 parent/principal 的方法。类似于 SetPrincipalEntityType()
.
当然我总是可以手动删除 Post
,但我正在努力避免这种情况。
有什么想法吗?
工作示例
我创建了一个带有集成测试的 test project,您可以使用它来重现问题:
> git clone https://github.com/bert2/ef-vs-garbage-db.git
> cd .\ef-vs-garbage-db\
> dotnet test
测试 DeletesTextWhenDeletingReview 将失败。
我在 EF Core GitHub 页面上发布了同样的问题。 solution posted there 的想法是在 SaveChanges()
的覆盖中找到孤立的实体并手动删除它们:
public override int SaveChanges(bool acceptAllChangesOnSuccess)
{
// Note that this is internal code to force cascade deletes to happen.
// It may stop working in any future release.
ChangeTracker.DetectChanges();
this.GetService<IStateManager>().GetEntriesToSave();
try
{
ChangeTracker.AutoDetectChangesEnabled = false;
foreach (var entry in ChangeTracker
.Entries<CritiqueText>()
.Where(e => Entry(e.Entity.Review).State == EntityState.Deleted))
{
entry.State = EntityState.Deleted;
}
}
finally
{
ChangeTracker.AutoDetectChangesEnabled = true;
}
return base.SaveChanges(acceptAllChangesOnSuccess);
}
我利用它构建了一个基于属性的解决方案,您可以在 test project repo.
中找到它
考虑遗留数据库的这个(诚然相当混乱)实体模型:
如您所见,它对 Blog
建模,其中包含 Entry
的集合,每个指向一个博客 Post
。箭头的尖端指向关系的principal/parent。至少这是 EF 核心根据存储外键的位置看到它的方式。
不幸的是,Entry
和 Post
之间的 FK 是错误的方式,因为 Post
实际上是 Entry
的 child;即当我从 Blog
中删除 Entry
时,关联的 Post
也应该被删除。
由于我无法更改遗留数据库的模式,我想知道如何说服 EF 到 cascade-delete Post
s,尽管它不认为它们是 children Entry
s.
我尝试明确指定 DeleteBehavior
但无济于事:
modelBuilder.Entity<Entry>()
.HasOne(e => e.Post)
.WithOne(p => p.Entry)
.OnDelete(DeleteBehavior.Cascade);
我还尝试使导航 属性 Post.Entry
成为必需,以指示 Post
不能没有 Entry
,但这也无济于事:
public class Post {
// ...
[Required]
[InverseProperty(nameof(Entry.Post))]
public Entry Entry { get; set; }
}
我目前正在检查 owned entity types 是否有帮助,但我持怀疑态度。
我想我需要的是一种明确说明实体关系的 parent/principal 的方法。类似于 SetPrincipalEntityType()
.
当然我总是可以手动删除 Post
,但我正在努力避免这种情况。
有什么想法吗?
工作示例
我创建了一个带有集成测试的 test project,您可以使用它来重现问题:
> git clone https://github.com/bert2/ef-vs-garbage-db.git
> cd .\ef-vs-garbage-db\
> dotnet test
测试 DeletesTextWhenDeletingReview 将失败。
我在 EF Core GitHub 页面上发布了同样的问题。 solution posted there 的想法是在 SaveChanges()
的覆盖中找到孤立的实体并手动删除它们:
public override int SaveChanges(bool acceptAllChangesOnSuccess)
{
// Note that this is internal code to force cascade deletes to happen.
// It may stop working in any future release.
ChangeTracker.DetectChanges();
this.GetService<IStateManager>().GetEntriesToSave();
try
{
ChangeTracker.AutoDetectChangesEnabled = false;
foreach (var entry in ChangeTracker
.Entries<CritiqueText>()
.Where(e => Entry(e.Entity.Review).State == EntityState.Deleted))
{
entry.State = EntityState.Deleted;
}
}
finally
{
ChangeTracker.AutoDetectChangesEnabled = true;
}
return base.SaveChanges(acceptAllChangesOnSuccess);
}
我利用它构建了一个基于属性的解决方案,您可以在 test project repo.
中找到它