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
以及相同的 Header
和 Footer
。当您尝试删除 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
方法。
我对以下型号有疑问:
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
以及相同的 Header
和 Footer
。当您尝试删除 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
方法。