entity-framework-core - 删除子项时出现“实体之间的关联已被切断”错误,关系根据复合键定义
问题描述
所以前言:我有一对模型,一个被定义为“父”的模型,它有自己的 ID、属性和子组件的字段:
public class SearchDesign
{
[Key]
[DataMember]
public int Id {get; set;}
// ... more properties
[DataMember]
public virtual ICollection<SearchComponent> SearchComponents { get; set; }
}
组件本身是 的子级SearchDesign
,并且设置为也可以表示为树。
public class SearchComponent
{
[Key]
[DataMember]
public int Id { get; set; }
[Key]
[DataMember]
public int SearchDesignID { get; set; }
[Required]
[DataMember]
public int Order { get; set; }
[DataMember]
public int? ParentID { get; set; }
// ... more properties
[DataMember]
public virtual SearchDesign SearchDesign { get; set; }
[DataMember]
public virtual SearchComponent ParentComponent { get; set; }
[DataMember]
public virtual ICollection<SearchComponent> ChildComponents { get; set; }
}
模型的这些特定部分是在 DBContext 中设置的,如下所示......
modelBuilder.Entity<SearchComponent>()
.HasKey(sc => new { sc.Id, sc.SearchDesignID });
modelBuilder.Entity<SearchComponent>()
.HasMany(parent => parent.ChildComponents)
.WithOne(child => child.ParentComponent)
.HasForeignKey(parent => new { parent.ParentID, parent.SearchDesignID })
.IsRequired(false)
.OnDelete(DeleteBehavior.Restrict);
modelBuilder.Entity<SearchComponent>()
.HasOne(sc => sc.SearchDesign)
.WithMany(sd => sd.SearchComponents)
.HasForeignKey(sc => sc.SearchDesignID)
.IsRequired(false);
因此,有一点我尝试通过 API 执行更新函数,该 API 调用一个函数,该函数将覆盖我们数据库中现有实体的特征。在执行此操作的过程中,我们从设计中“删除”相关组件,然后以反映在来自请求的输入中的状态重新添加它们:
//Update case.
design = design.Clone(); //function that un-links the entity from EF core by using Newtonsoft to de-serialize and then re-serialize it.
SearchDesign existing = LoadDesign(design.Id);
if (existing == null)
{
throw new ArgumentOutOfRangeException();
}
else
{
existing.Name = design.Name;
...
//Remove and re-add.
DBContext.SearchComponents.RemoveRange(existing.SearchComponents); //THIS throws the error.
foreach (var component in design.SearchComponents)
{
component.SearchDesign = existing;
}
DBContext.SearchComponents.AddRange(design.SearchComponents);
}
问题是,当我尝试RemoveRange
(我重申)尝试删除依赖元素时,它会引发此错误:
System.InvalidOperationException:实体“SearchDesign”和“SearchComponent”与键值“{SearchDesignID: 7}”之间的关联已被切断,但该关系被标记为“必需”或隐式需要,因为外键不可为空. 如果在切断所需关系时应删除依赖/子实体,则设置关系以使用级联删除。
我的直觉让我相信这可能与SearchComponent
结构上的父/子循环有关,但错误似乎表明这是与设计/组件链接有关的问题?如果情况确实如此,那么这个错误就没有任何意义,因为我正在尝试删除依赖/子实体,而不是父实体,并且我不能完全将SearchDesignID
get 设置为 null,因为这是元素键的一部分。
EF Core 专家的任何建议将不胜感激,谢谢!
解决方案
我的直觉让我相信这可能与 SearchComponent 结构上的父/子循环有关
事实证明这是正确的。主要问题是 RootDBContext 定义的这一部分:
modelBuilder.Entity<SearchComponent>()
.HasMany(parent => parent.ChildComponents)
.WithOne(child => child.ParentComponent)
.HasForeignKey(parent => new { parent.ParentID, parent.SearchDesignID })
.IsRequired(false)
.OnDelete(DeleteBehavior.Restrict);
限制删除行为是导致错误生成的原因。将其更改为DeleteBehavior.Cascade
导致问题停止发生......在单元测试中。
这产生了另一个问题,其中由于更改而创建的迁移无法应用于我的本地环境数据库,因为迁移脚本会提供有关删除级联循环的错误。问题是,我研究过的关于在 EF Core 中实现自引用树结构的其他在线资源使用了该DeleteBehavior.Restrict
设置,这就是原因。
因此,真正的解决方案是手动完成工作 - 因为DbSet.RemoveRange
通常用于删除不同实体之间的非递归关系,它可能会产生错误,因为它无法识别我的哪些SearchComponent
实体是孩子,哪些是孩子是父母。使用非常基本的深度优先树遍历似乎绕过了依赖问题:
private void DeleteTheTree(ICollection<SearchComponent> componentsToDelete)
{
foreach (SearchComponent component in componentsToDelete)
{
if (component.ChildComponents != null)
{
DeleteTheTree(component.ChildComponents);
}
DBContext.SearchComponents.Remove(component);
}
}
推荐阅读
- postgresql - 反向 SELECT 查询结果
- java - 如何模拟 Unirest 链接方法?
- java - 更改 MultivalueMap 键会引发 ConcurrentModificationException
- python - 获取“需要整数(获取类型元组)”错误,使用 cv2 绘制矩形
- javascript - 单元测试 KeyboardEvent 返回“isTrusted: false”和“无法读取属性 'nextElementSibling' of null”
- android - Appium 测试安卓
- php - 使用 PHP readfile() 下载适用于 Firefox,但不适用于 Chrome
- php - cURL 获取 html 页面结果为空
- mysql - 按月除以日期列表 SQL
- csv - csv 文件使用加载数据到 hive 表 - 如何格式化 csv 中的日期以由 hive 表接受