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

SB2*_*055 275 .net entity-framework entity-framework-4 ef-code-first

我已经和它搏斗了一段时间,并且无法弄清楚发生了什么.我有一个卡片实体,其中包含Sides(通常为2个) - 卡片和侧面都有一个舞台.我正在使用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; }
}
Run Code Online (Sandbox Code Playgroud)

这是我的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; }

}
Run Code Online (Sandbox Code Playgroud)

这是我的舞台实体:

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; } }
}
Run Code Online (Sandbox Code Playgroud)

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

    public int? SideId { get; set; }
    [ForeignKey("SideId")]
    public virtual Side Side { get; set; }
Run Code Online (Sandbox Code Playgroud)

迁移成功运行.如果我打开SSMS并查看表格,我可以看到Stage_StageId已经添加到Cards(如预期/期望的那样),但是不Sides包含对Stage(不是预期的)的引用.

如果我再添加

    [Required]
    [ForeignKey("StageId")]
    public virtual Stage Stage { get; set; }
    public int StageId { get; set; }
Run Code Online (Sandbox Code Playgroud)

对于我的Side类,我看到StageId列已添加到我的Side表中.

这是有效的,但现在在我的应用程序中,任何对Stage包含a的引用SideId,在某些情况下完全无关紧要. 我想基于上面的Stage类给我CardSide实体一个Stage属性,如果可能的话,不用参考属性污染舞台类 ......我做错了什么?

Sla*_*uma 354

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

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

所以,你必须从两个级联删除路径StageSide-这将导致异常.

您必须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);
Run Code Online (Sandbox Code Playgroud)

  • 这是他们实施的限制吗?对于我来说,删除'Stage`以直接和通过`Card`级联到`Side`似乎很好 (4认同)
  • 谢谢Slauma.如果我使用上面演示的流畅API,其他字段是否会保留其级联删除行为?例如,当删除卡时,我仍然需要删除Sides. (2认同)
  • 有什么方法可以知道哪些属性导致了错误?我遇到了同样的问题,看着我的课程,我看不到循环在哪里 (2认同)

Cem*_*tlu 52

我有一张与其他人有圆形关系的桌子,我也遇到了同样的错误.原来它是关于不可空的外键.如果key不可为空,则必须删除相关对象,并且循环关系不允许这样做.所以使用可以为空的外键.

[ForeignKey("StageId")]
public virtual Stage Stage { get; set; }
public int? StageId { get; set; }
Run Code Online (Sandbox Code Playgroud)

  • 如果您不希望将Stage设置为null(Stage是原始问题中的必填字段),则不应执行此操作. (5认同)
  • 我删除了[Required]标签,但另一个重要的事情是使用`int?`而不是`int`让它可以为空. (4认同)

Nex*_*s23 31

有人想知道如何在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.....
Run Code Online (Sandbox Code Playgroud)

  • 或者,`builder.HasOne(x => x.Stage).WithMany().HasForeignKey(x => x.StageId).OnDelete(DeleteBehavior.Restrict);` (10认同)
  • 这将关闭所有关系上的级联删除。对于某些用例,级联删除可能是理想的功能。 (2认同)

Sea*_*ean 21

当我从EF7模型迁移到EF6版本时,我收到了很多实体的错误.我不想一次一个地浏览每个实体,所以我使用了:

builder.Conventions.Remove<ManyToManyCascadeDeleteConvention>();
builder.Conventions.Remove<OneToManyCascadeDeleteConvention>();
Run Code Online (Sandbox Code Playgroud)

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

Mus*_*yed 17

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

AddForeignKey("dbo.Stories", "StatusId", "dbo.Status", "StatusID", cascadeDelete: false);
Run Code Online (Sandbox Code Playgroud)

  • @Mussakkhir谢谢你的回答.你的方式是非常优雅和更多 - 它更准确,直接针对我面临的问题! (2认同)

roc*_*ker 6

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

remove-migration然后我开始一一进行,直到程序包管理器控制台向我抛出异常:

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

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

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


Usm*_*han 6

我修好了.添加迁移时,在Up()方法中会有如下所示的行:

.ForeignKey("dbo.Members", t => t.MemberId, cascadeDelete:True)
Run Code Online (Sandbox Code Playgroud)

如果你只是从最后删除cascadeDelete它将工作.


sgr*_*oft 6

只是为了文档的目的,对于未来的人来说,这件事可以像这样简单地解决,用这个方法,你可以做一个禁用一次的方法,你可以正常访问你的方法

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

protected override void OnModelCreating(DbModelBuilder modelBuilder) {
    modelBuilder.Conventions.Remove<OneToManyCascadeDeleteConvention>();
}
Run Code Online (Sandbox Code Playgroud)


jon*_*.js 5

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

就我而言,我不想删除密钥删除的依赖记录.如果在您的情况下就是这种情况,只需将迁移中的布尔值更改为false:

AddForeignKey("dbo.Stories", "StatusId", "dbo.Status", "StatusID", cascadeDelete: false);
Run Code Online (Sandbox Code Playgroud)

如果你正在创建抛出这个编译器错误但是想要保持级联删除的关系,那么很有可能; 你的人际关系有问题.


小智 5

在.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);
            });
Run Code Online (Sandbox Code Playgroud)