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


295

我已经为此努力了一段时间,无法完全了解发生了什么。我有一个包含Sides(通常为2)的Card实体-Cards和Sides都有一个Stage。我正在使用EF Codefirst迁移,并且迁移因以下错误而失败:

在表“ Sides”上引入FOREIGN KEY约束“ FK_dbo.Sides_dbo.Cards_CardId”可能会导致循环或多个级联路径。指定ON DELETE NO ACTION或ON UPDATE NO ACTION,或修改其他FOREIGN KEY约束。

这是我的银行实体:

public class Card
{
    public Card()
    {
        Sides = new Collection<Side>();
        Stage = Stage.ONE;
    }

    [Key]
    [Required]
    public virtual int CardId { get; set; }

    [Required]
    public virtual Stage Stage { get; set; }

    [Required]
    [ForeignKey("CardId")]
    public virtual ICollection<Side> Sides { get; set; }
}

这是我的Side实体:

public class Side
{
    public Side()
    {
        Stage = Stage.ONE;
    }

    [Key]
    [Required]     
    public virtual int SideId { get; set; } 

    [Required]
    public virtual Stage Stage { get; set; }

    [Required]
    public int CardId { get; set; }

    [ForeignKey("CardId")]
    public virtual Card Card { get; set; }

}

这是我的舞台实体:

public class Stage
{
    // Zero
    public static readonly Stage ONE = new Stage(new TimeSpan(0, 0, 0), "ONE");
    // Ten seconds
    public static readonly Stage TWO = new Stage(new TimeSpan(0, 0, 10), "TWO");

    public static IEnumerable<Stage> Values
    {
        get
        {
            yield return ONE;
            yield return TWO;
        }

    }

    public int StageId { get; set; }
    private readonly TimeSpan span;
    public string Title { get; set; }

    Stage(TimeSpan span, string title)
    {
        this.span = span;
        this.Title = title;
    }

    public TimeSpan Span { get { return span; } }
}

奇怪的是,如果我在Stage类中添加以下内容:

    public int? SideId { get; set; }
    [ForeignKey("SideId")]
    public virtual Side Side { get; set; }

迁移成功运行。如果我打开SSMS并查看表格,则可以看到它Stage_StageId已被添加到Cards(按预期/期望的方式),但是未Sides包含对Stage(非预期的)引用。

如果我再加上

    [Required]
    [ForeignKey("StageId")]
    public virtual Stage Stage { get; set; }
    public int StageId { get; set; }

在Side类中,我看到StageId列已添加到Side表中。

这是可行的,但是现在在我的整个应用程序中,对的任何引用都Stage包含SideId,在某些情况下完全不相关。 我只想给我CardSide实体一个Stage基于上述Stage类的属性,如果可能的话,不要用引用属性污染Stage类 ……我在做什么错?


7
通过在引用中允许使用空值来禁用级联删除...因此在Side类中添加Nullable整数并删除[Required]属性=>public int? CardId { get; set; }
Jaider 2014年

2
在EF Core中,应使用DeleteBehavior.Restrict或禁用级联删除DeleteBehavior.SetNull
新浪乐透

Answers:


371

因为Stage必需的,所以Stage默认情况下,涉及的所有一对多关系都将启用级联删除。这意味着,如果您删除Stage实体

  • 删除将直接级联到 Side
  • 删除将直接级联到Card,因为默认情况下启用了级联删除,Card并且Side具有必需的一对多关系,然后它将从级联Card到。Side

因此,您有两个从Stage到的级联删除路径Side-这会导致异常。

您必须Stage在至少一个实体中将其设置为可选(即,[Required]Stage属性中删除属性),或者使用Fluent API禁用级联删除(对于数据注释而言是不可能的):

modelBuilder.Entity<Card>()
    .HasRequired(c => c.Stage)
    .WithMany()
    .WillCascadeOnDelete(false);

modelBuilder.Entity<Side>()
    .HasRequired(s => s.Stage)
    .WithMany()
    .WillCascadeOnDelete(false);

2
谢谢Slauma。如果如上所述,我使用了流畅的API,其他字段是否会保留其级联删除行为?例如,删除卡后,我仍然需要删除面。
SB2055 2013年

1
@ SB2055:是的,它只会影响与的关系Stage。其他关系保持不变。
Slauma

2
有什么办法可以知道导致错误的属性吗?我遇到了同样的问题,看着我的课,我看不到周期在哪里
罗德里戈·华雷斯

4
这是其实施的限制吗?对于我来说,Stage删除似乎Side可以直接并通过Card
aaaaaaa

1
假设我们将CascadeOnDelete设置为false。然后,我们删除了与卡记录之一相关的阶段记录。Card.Stage(FK)会怎样?是否保持不变?还是设置为Null?
ninbit

61

我有一个与其他人有循环关系的表,并且遇到了同样的错误。原来这是关于不能为空的外键。如果key不为null,则必须删除相关对象,并且循环关系不允许这样做。因此,请使用可为空的外键。

[ForeignKey("StageId")]
public virtual Stage Stage { get; set; }
public int? StageId { get; set; }

5
我删除了[Required]标签,但另一个重要的事情是使用int?而不是int使其可以为空。
VSB

1
我尝试了多种不同的方法来关闭级联删除,但没有任何效果-可以解决此问题!
ambog36

5
如果您不希望将Stage设置为null(Stage是原始问题中的必填字段),则不应执行此操作。
cfwall

35

任何人都想知道如何在EF核心中做到这一点:

      protected override void OnModelCreating(ModelBuilder modelBuilder)
            {
                foreach (var relationship in modelBuilder.Model.GetEntityTypes().SelectMany(e => e.GetForeignKeys()))
                {
                    relationship.DeleteBehavior = DeleteBehavior.Restrict;
                }
           ..... rest of the code.....

3
这将关闭所有关系上的级联删除。对于某些用例,级联删除可能是理想的功能。
Blaze

15
或者,builder.HasOne(x => x.Stage).WithMany().HasForeignKey(x => x.StageId).OnDelete(DeleteBehavior.Restrict);
饼干

@Biscuits扩展方法可能会随着时间的推移而改变,或者您忘记了builder _ .Entity<TEntity>() _以前的HasOne() 方法……
ViRuSTriNiTy

1
@ViRuSTriNiTy,我的代码段是2岁。但是,我认为您是对的-如今,这是您选择实施的时候IEntityTypeConfiguration<T>。我不记得builder.Entity<T>那些日子见过这种方法,但是我可能是错的。尽管如此,它们都可以工作:)
饼干

21

从EF7模型迁移到EF6版本时,很多实体都遇到了这个错误。我不想一次遍历每个实体,所以我使用了:

builder.Conventions.Remove<ManyToManyCascadeDeleteConvention>();
builder.Conventions.Remove<OneToManyCascadeDeleteConvention>();

2
这应该添加到从DbContext继承的类中,例如OnModelCreating方法中。该构建器的类型为DbModelBuilder
CodingYourLife

这对我有用;.NET 4.7,EF6。我遇到了一个绊脚石,因此,当我通过删除这些约定的迁移脚本重新生成时,它似乎没有任何帮助。使用“ -Force”运行“ Add-Migration”将清除所有内容,并重新构建它,包括上面的这些约定。问题已解决……
詹姆斯·乔伊斯

.net核心中不存在这些功能,那里是否有等效功能?
jjxtra


20

您可以将cascadeDelete设置为false或true(在迁移Up()方法中)。取决于您的要求。

AddForeignKey("dbo.Stories", "StatusId", "dbo.Status", "StatusID", cascadeDelete: false);

2
@Mussakkhir谢谢您的回答。您的方式非常优雅,而且可以解决更多问题-更准确,直接针对我所遇到的问题!
Nozim Turakulov '16

只是不要忘记该UP方法可能会被外部操作修改。
Dementic

8

在.NET Core中,我将onDelete选项更改为ReferencialAction.NoAction

         constraints: table =>
            {
                table.PrimaryKey("PK_Schedule", x => x.Id);
                table.ForeignKey(
                    name: "FK_Schedule_Teams_HomeId",
                    column: x => x.HomeId,
                    principalTable: "Teams",
                    principalColumn: "Id",
                    onDelete: ReferentialAction.NoAction);
                table.ForeignKey(
                    name: "FK_Schedule_Teams_VisitorId",
                    column: x => x.VisitorId,
                    principalTable: "Teams",
                    principalColumn: "Id",
                    onDelete: ReferentialAction.NoAction);
            });

7

我也遇到了这个问题,我通过类似线程的回答立即解决了它

就我而言,我不想在删除键时删除从属记录。如果是这种情况,只需将迁移中的布尔值更改为false即可:

AddForeignKey("dbo.Stories", "StatusId", "dbo.Status", "StatusID", cascadeDelete: false);

如果您正在创建会引发此编译器错误的关系,但您确实希望维护级联删除,则可能会出现这种情况;您的人际关系有问题。


6

我修好了。当您添加迁移时,在Up()方法中将出现如下一行:

.ForeignKey("dbo.Members", t => t.MemberId, cascadeDelete:True)

如果只从最后删除cascadeDelete,它将起作用。


5

仅出于文档目的,对于将来的某个人,可以这样简单地解决此问题,并且使用此方法,您可以执行一次禁用一次的方法,并且可以正常访问该方法

将此方法添加到上下文数据库类中:

protected override void OnModelCreating(DbModelBuilder modelBuilder) {
    modelBuilder.Conventions.Remove<OneToManyCascadeDeleteConvention>();
}

1

这听起来很奇怪,我不知道为什么,但是在我的情况下,这是因为我的ConnectionString使用的是“。” 在“数据源”属性中。一旦将其更改为“ localhost”,它就会像魅力一样工作。无需其他更改。


1

.NET Core中,我尝试了所有较高的答案-但没有成功。我对数据库结构进行了很多更改,每次添加新的迁移尝试以update-database,都会收到相同的错误。

然后我开始remove-migration一个接一个地工作,直到Package Manager Console抛出异常:

迁移'20170827183131 _ ***'已被应用到数据库

之后,我添加了新的迁移(add-migration),并update-database 成功

所以我的建议是:清除所有临时迁移,直到当前数据库状态为止。


1

现有答案非常棒,我只是想补充一下,由于其他原因,我遇到了这个错误。我想在现有数据库上创建初始EF迁移,但未使用-IgnoreChanges标志,在一个空数据库上应用了Update-Database命令(也存在于现有数据库上)。

相反,当当前数据库结构为当前结构时,我必须运行此命令:

Add-Migration Initial -IgnoreChanges

db结构中可能存在真正的问题,但一次将世界节省了一步。


1

最简单的方法是,编辑您的文件迁移(cascadeDelete: true)(cascadeDelete: false)那么在你的包管理器Console.if分配后的更新,数据库命令时,它的问题,您最后一次的迁移,那么所有的权利。否则,请检查您以前的迁移历史记录,复制这些内容,然后粘贴到您的最后一个迁移文件中,然后再执行相同的操作。它对我来说非常有效。


1
public partial class recommended_books : DbMigration
{
    public override void Up()
    {
        CreateTable(
            "dbo.RecommendedBook",
            c => new
                {
                    RecommendedBookID = c.Int(nullable: false, identity: true),
                    CourseID = c.Int(nullable: false),
                    DepartmentID = c.Int(nullable: false),
                    Title = c.String(),
                    Author = c.String(),
                    PublicationDate = c.DateTime(nullable: false),
                })
            .PrimaryKey(t => t.RecommendedBookID)
            .ForeignKey("dbo.Course", t => t.CourseID, cascadeDelete: false) // was true on migration
            .ForeignKey("dbo.Department", t => t.DepartmentID, cascadeDelete: false) // was true on migration
            .Index(t => t.CourseID)
            .Index(t => t.DepartmentID);

    }

    public override void Down()
    {
        DropForeignKey("dbo.RecommendedBook", "DepartmentID", "dbo.Department");
        DropForeignKey("dbo.RecommendedBook", "CourseID", "dbo.Course");
        DropIndex("dbo.RecommendedBook", new[] { "DepartmentID" });
        DropIndex("dbo.RecommendedBook", new[] { "CourseID" });
        DropTable("dbo.RecommendedBook");
    }
}

当迁移失败时,您会得到两个选择:'在表'RecommendedBook'上引入FOREIGN KEY约束'FK_dbo.RecommendedBook_dbo.Department_DepartmentID'可能会导致循环或多个级联路径。指定ON DELETE NO ACTION或ON UPDATE NO ACTION,或修改其他FOREIGN KEY约束。无法创建约束或索引。请参阅先前的错误。

这是通过在迁移文件中将“ cascadeDelete”设置为false然后运行“ update-database”来使用“修改其他FOREIGN KEY约束”的示例。


0

前述解决方案均不适合我。我要做的是在不需要的外键上使用可为null的int(int?)(或不是非null的列键),然后删除一些迁移。

首先删除迁移,然后尝试可为null的int。

问题既是修改又是模型设计。无需更改代码。


By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.