Entity Framework 级联删除 - FOREIGN KEY 约束

Entity Framework Cascade delete - FOREIGN KEY constraint

我对以下型号有疑问:

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);

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

Introducing FOREIGN KEY constraint 'FK_dbo.ProjectPages_dbo.ProjectPages_FooterId' on table 'ProjectPages' may cause cycles or multiple cascade paths. Specify ON DELETE NO ACTION or ON UPDATE NO ACTION, or modify other FOREIGN KEY constraints.

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

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

您可以通过使用 Fluent API 在两个关系之一中禁用级联删除或将某些关系定义为可选关系(使用可为空的外键,但您不能配置与级联删除的关系)。

更新

是的,您将两个 FK 作为可选项,但两个关系都配置了级联删除,这就是 EF 抛出该异常的原因。我的建议是只设置一个与级联删除的关系。关于其他关系,恐怕您必须手动进行。例如,如果您选择 Footer 手动删除,那么当您要删除 ProjectPage 时,您必须将 Footer 属性 设置为 null.这里的问题是您的数据库中可能有孤儿。为避免这种情况,您可以覆盖上下文中的 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方法。