实体框架级联删除 - FOREIGN KEY 约束

Posted

技术标签:

【中文标题】实体框架级联删除 - FOREIGN KEY 约束【英文标题】:Entity Framework Cascade delete - FOREIGN KEY constraint 【发布时间】:2015-04-29 03:53:46 【问题描述】:

我对以下型号有疑问:

public class ProjectPage

    [Key]
    public Guid Id  get; set; 

    public Guid? HeaderId  get; set; 
    public ProjectPage Header  get; set; 

    public Guid? FooterId  get; set; 
    public ProjectPage Footer  get; set; 

在创建模型时我有这个:

modelBuilder.Entity<ProjectPage>().HasOptional(p => p.Header).WithMany().HasForeignKey(p => p.HeaderId).WillCascadeOnDelete(true);
modelBuilder.Entity<ProjectPage>().HasOptional(p => p.Footer).WithMany().HasForeignKey(p => p.FooterId).WillCascadeOnDelete(true);

但我无法更新数据库。我在包管理器控制台中遇到以下错误:

引入 FOREIGN KEY 约束 'FK_dbo.ProjectPages_dbo.ProjectPages_FooterId' 在桌子上 “ProjectPages”可能会导致循环或多个级联路径。指定开 DELETE NO ACTION 或 ON UPDATE NO ACTION,或修改其他 FOREIGN KEY 约束。

有人可以解释如何删除项目页面(可以是另一个项目页面中的页脚或页眉)吗?

【问题讨论】:

【参考方案1】:

当您有多个级联删除路径可能会结束尝试删除数据库中的同一行时,会导致该异常。想象一下,如果您有 ProjectPage 与相同的 HeaderFooter。当您尝试删除 ProjectPage 时,由于您的关系配置,将有两条路径尝试删除 DB 中的同一行(一条用于Header,另一条用于Footer)。

您可以通过使用Fluent API 禁用两个关系之一中的级联删除或将某些关系定义为可选(使用可为空的外键,但您不能使用级联删除配置关系)来避免此类不明确的删除路径)。

更新

是的,您将两个 FK 作为可选项,但两个关系都配置了级联删除,这就是 EF 抛出该异常的原因。我的建议是只设置与级联删除的一种关系。关于其他关系,恐怕你必须手动完成。例如,如果您选择手动删除Footer,那么当您要删除ProjectPage 时,您必须将Footer 属性设置为null。这里的问题是您的数据库中可能有孤儿。为避免这种情况,您可以覆盖 Context 上的 SaveChanges 以查找和删除孤儿:

public override int SaveChanges()

  ProjectPages
    .Local
    .Where(r => r.Footer== null && r.FooterId!=default(Guid)).Select(r=>r.FooterId)
    .ToList()
    .ForEach(id => ProjectPages.Remove(ProjectPages.Find(id)));

  return base.SaveChanges();

另一种方法是将FooterId 设置为default(Guid) 值。由于您的 PK 属性 (Id) 的类型是 Guid 并且它不是身份,您必须在将 ProjectPage 添加到数据库之前设置该属性。因此,将FooterId 设置为default(Guid) 是另一种标记要删除的实体的方法。如果您选择此变体,您的 SaveChanges 方法可能如下所示:

 public override int SaveChanges()
 
  ProjectPages
    .Local
    .Where(r => r.Footer!= null && r.FooterId!=default(Guid)).Select(r=>r.Footer)
    .ToList()
    .ForEach(pp=> ProjectPages.Remove(pp));

  return base.SaveChanges();
 

或者:

 public override int SaveChanges()
 
  ProjectPages
    .Local
    .Where(r => r.Footer!= null && r.FooterId!=default(Guid)).Select(r=>r.Footer)
    .ToList()
    .ForEach(pp=> Entry(pp).State=EntityState.Deleted);

  return base.SaveChanges();
 

这样可以避免调用Find方法。

【讨论】:

但是当我删除级联删除时,它告诉我,该行具有依赖关系并且无法删除...我有可选的外键。 所以在我从表中删除页脚或页眉之前,我需要自己在所有依赖项中创建空页眉或页脚? 你好@AndreyMykhaylov,我已经更新了我的答案,希望现在对你有所帮助。

以上是关于实体框架级联删除 - FOREIGN KEY 约束的主要内容,如果未能解决你的问题,请参考以下文章

引入 FOREIGN KEY 约束可能会导致循环或多个级联路径 - 为啥?

可能会导致循环或多个级联路径。指定ON DELETE NO ACTION或ON UPDATE NO ACTION,或修改其他FOREIGN KEY约束

SQL Server 引入 FOREIGN KEY 约束可能导致循环或多个级联路径

错误:引入FOREIGN KEY约束可能会导致循环或多个级联路径

在表“ReservedSeats”上引入 FOREIGN KEY 约束“FK_ReservedSeats_Seats_SeatId”可能会导致循环或多个级联路径

ASP.Net MVC 3 EF“在表上引入 FOREIGN KEY 约束可能会导致循环或多个级联路径”