【问题标题】:'The association between entites has been severed' error when deleting child, with relationship defined in terms of compound key删除子项时出现“实体之间的关联已被切断”错误,关系根据复合键定义
【发布时间】:2021-03-17 21:52:03
【问题描述】:

所以前言:我有一对模型,一个被定义为“父”,它有自己的 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 设置为 null,因为这是元素的一部分键。

我们将不胜感激 EF Core 专家的任何建议,谢谢!

【问题讨论】:

  • 这里的问题是:“隐含需要,因为外键不可为空”。 SearchDesignID 必须可以为空才能使关系成为可选。
  • 对,但是SearchComponent(依赖实体)上的SearchDesignID 本质上并不意味着可以为空。如果没有附加的设计,它就不应该存在。它还用作具有自己 ID 的元素的复合键,我不确定 SQL 表中元素的键(或构成该键的东西)是否可以为空?

标签: entity-framework-core


【解决方案1】:

我的直觉让我相信这可能与 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 通常用于删除不同实体之间的非递归关系,它可能会生成错误,因为它无法识别我的哪些@ 987654325@ 个实体是孩子,哪些是父母。使用非常基本的深度优先树遍历似乎绕过了依赖问题:

private void DeleteTheTree(ICollection<SearchComponent> componentsToDelete)
{
    foreach (SearchComponent component in componentsToDelete)
    {
        if (component.ChildComponents != null)
        {
            DeleteTheTree(component.ChildComponents);
        }
        DBContext.SearchComponents.Remove(component);
    }
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2020-12-21
    • 2020-07-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-08-01
    • 1970-01-01
    相关资源
    最近更新 更多