【问题标题】:Introducing FOREIGN KEY constraint may cause cycles or multiple cascade paths - why?引入 FOREIGN KEY 约束可能会导致循环或多个级联路径 - 为什么?
【发布时间】:2013-06-12 05:11:38
【问题描述】:

我已经为此苦苦挣扎了一段时间,无法完全弄清楚发生了什么。我有一个卡片实体,其中包含边(通常是 2 个)-卡片和边都有一个阶段。我正在使用 EF Codefirst 迁移,迁移失败并出现此错误:

引入 FOREIGN KEY 约束“FK_dbo.Sides_dbo.Cards_CardId” 表“边”可能会导致循环或多个级联路径。指定开 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 属性,而不用引用属性污染舞台类...我是什么做错了吗?

【问题讨论】:

  • 通过允许引用中的空值来禁用级联删除...所以在Side 类中添加可空整数并删除[Required] 属性=> public int? CardId { get; set; }
  • 在 EF Core 中,您应该使用 DeleteBehavior.RestrictDeleteBehavior.SetNull 禁用级联删除。
  • 接受的答案是唯一正确的答案。问题是:如果我想要 required 关系,如何防止循环级联路径。一个简单的映射指令就足够了。因此,不建议将关系设为可选,或者更糟糕的是,编辑生成的迁移文件(在 db 模型和概念模型之间引入差异),或者更糟糕的是,禁用所有级联删除。

标签: .net entity-framework foreign-keys ef-code-first entity-framework-4


【解决方案1】:

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

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

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

您必须在至少一个实体中使Stage 可选(即从Stage 属性中删除[Required] 属性)或使用Fluent API 禁用级联删除(数据注释不可能):

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

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

【讨论】:

  • 谢谢斯劳马。如果我使用上面演示的 fluent API,其他字段会保留它们的级联删除行为吗?例如,当卡片被删除时,我仍然需要删除 Sides。
  • @SB2055:是的,它只会影响来自Stage的关系。其他关系保持不变。
  • 有什么方法可以知道是哪些属性导致了错误?我也有同样的问题,看着我的课,我看不出循环在哪里
  • 这是他们实施的限制吗?对我来说,Stage 删除直接和通过Card 级联到Side 似乎很好
  • 假设我们将 CascadeOnDelete 设置为 false。然后我们删除了与其中一个 Card 记录相关的阶段记录。 Card.Stage (FK) 会发生什么?它保持不变吗?还是设置为 Null?
【解决方案2】:

我也遇到了这个问题,我马上用this answer from a similar thread解决了

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

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

如果您创建的关系可能会引发此编译器错误,但您希望保持级联删除,则很有可能;你的人际关系有问题。

【讨论】:

    【解决方案3】:

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

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

    【讨论】:

    • @Mussakkhir 谢谢你的回答。你的方式非常优雅,而且更全面——更准确,更直接针对我面临的问题!
    • 别忘了UP方法可能会被外部操作修改。
    【解决方案4】:

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

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

    【讨论】:

    • 我删除了 [Required] 标签,但另一个重要的事情是使用 int? 而不是 int 让它可以为空。
    • 我尝试了许多不同的方法来关闭级联删除,但没有任何效果 - 这解决了它!
    • 如果您不想让 Stage 设置为 null,则不应执行此操作(Stage 是原始问题中的必填字段)。
    • 这是完美的修复。它拯救了我的一天!
    【解决方案5】:

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

    【讨论】:

      【解决方案6】:

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

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

      【讨论】:

      • 这应该添加到继承自 DbContext 的类中,例如在 OnModelCreating 方法中。构建器的类型为 DbModelBuilder
      • 这对我有用; .NET 4.7,EF 6。一个绊脚石是我得到了错误,所以当我通过删除这些约定的迁移脚本重新生成时,它似乎没有帮助。使用“-Force”运行“Add-Migration”会清除一切,并重建它,包括上面的这些约定。问题解决了……
      • .net core 中不存在那些,那里有任何等价物吗?
      【解决方案7】:

      任何想知道如何在 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.....
      

      【讨论】:

      • 这将关闭所有关系的级联删除。级联删除可能是某些用例所需的功能。
      • 或者,builder.HasOne(x =&gt; x.Stage).WithMany().HasForeignKey(x =&gt; x.StageId).OnDelete(DeleteBehavior.Restrict);
      • @Biscuits 扩展方法随时间而变化,或者您在调用HasOne() 之前忘记了builder _ .Entity&lt;TEntity&gt;() _...
      • @ViRuSTRiNiTy,我的 sn-p 已经 2 岁了。但是,我认为你是对的 - 现在它适用于你选择实施IEntityTypeConfiguration&lt;T&gt;。我不记得那些日子看到builder.Entity&lt;T&gt; 方法,但我可能是错的。尽管如此,它们都可以工作:)
      【解决方案8】:

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

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

      迁移“20170827183131_***”已应用于数据库

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

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

      【讨论】:

      • 这是给我的!尝试了级联行为的流体配置的所有变体,但我一直看到相同的 SQL 被执行,因为已经创建的迁移试图首先应用:/
      【解决方案9】:

      在 .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);
                  });
      

      【讨论】:

        【解决方案10】:

        现有的答案很好,我只是想补充一下,由于不同的原因,我遇到了这个错误。我想在现有数据库上创建初始 EF 迁移,但我没有使用 -IgnoreChanges 标志并将 Update-Database 命令应用于空数据库(也在现有数据库上失败)。

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

        Add-Migration Initial -IgnoreChanges
        

        db 结构中可能存在真正的问题,但一步一步拯救世界...

        【讨论】:

          【解决方案11】:

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

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

          问题在于修改和模型设计。无需更改代码。

          【讨论】:

            【解决方案12】:

            我解决了这个问题。添加迁移时,在 Up() 方法中会出现这样的一行:

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

            如果您只是从末尾删除 cascadeDelete,它将起作用。

            【讨论】:

              【解决方案13】:

              仅出于文档目的,对于未来的人来说,这件事可以像这样简单地解决,并且使用这种方法,您可以执行一次禁用的方法,并且您可以正常访问您的方法

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

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

              【讨论】:

                【解决方案14】:

                简单的方法是,将您的迁移文件(cascadeDelete: true) 编辑为(cascadeDelete: false),然后在您的包管理器控制台中分配更新数据库命令之后。如果您上次迁移有问题,那么没关系。否则检查你之前的迁移历史,复制这些东西,粘贴到你的最后一个迁移文件中,然后做同样的事情。它非常适合我。

                【讨论】:

                  【解决方案15】:
                  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”。

                  【讨论】:

                  • modelBuilder 中更改它比编辑自动生成的迁移要好得多。
                  【解决方案16】:

                  使您的外键属性可以为空。这会起作用的。

                  【讨论】:

                  • 那个问题下的cmets中的答案请在那里详细说明
                  【解决方案17】:

                  我遇到了同样的问题并卡了很长时间。以下步骤救了我。 通过约束并将 onDelete ReferentialActionCascade

                  更改为 NoAction
                    constraints: table =>
                    {
                        table.PrimaryKey("PK_table1", x => x.Id);
                        table.ForeignKey(
                           name: "FK_table1_table2_table2Id",
                           column: x => x.table2Id,
                           principalTable: "table2",
                           principalColumn: "Id",
                           onDelete: ReferentialAction.NoAction);
                    });
                  

                  【讨论】:

                    【解决方案18】:

                    在 .NET 5 OnModelCreating 中使用 .OnDelete(DeleteBehavior.Restrict),例如 @Nexus23 答案,但您不需要为每个模型禁用级联。

                    连接实体类型配置多对多示例:

                    internal class MyContext : DbContext
                    {
                        public MyContext(DbContextOptions<MyContext> options)
                            : base(options)
                        {
                        }
                    
                        public DbSet<Post> Posts { get; set; }
                        public DbSet<Tag> Tags { get; set; }
                    
                        protected override void OnModelCreating(ModelBuilder modelBuilder)
                        {
                            modelBuilder.Entity<Post>()
                                .HasMany(p => p.Tags)
                                .WithMany(p => p.Posts)
                                .UsingEntity<PostTag>(
                                    j => j
                                        .HasOne(pt => pt.Tag)
                                        .WithMany(t => t.PostTags)
                                        .HasForeignKey(pt => pt.TagId)
                                        .OnDelete(DeleteBehavior.Restrict),
                                    j => j
                                        .HasOne(pt => pt.Post)
                                        .WithMany(p => p.PostTags)
                                        .HasForeignKey(pt => pt.PostId)
                                        .OnDelete(DeleteBehavior.Restrict),
                                    j =>
                                    {
                                        j.Property(pt => pt.PublicationDate).HasDefaultValueSql("CURRENT_TIMESTAMP");
                                        j.HasKey(t => new { t.PostId, t.TagId });
                                    });
                        }
                    }
                    
                    public class Post
                    {
                        public int PostId { get; set; }
                        public string Title { get; set; }
                        public string Content { get; set; }
                    
                        public ICollection<Tag> Tags { get; set; }
                        public List<PostTag> PostTags { get; set; }
                    }
                    
                    public class Tag
                    {
                        public string TagId { get; set; }
                    
                        public ICollection<Post> Posts { get; set; }
                        public List<PostTag> PostTags { get; set; }
                    }
                    
                    public class PostTag
                    {
                        public DateTime PublicationDate { get; set; }
                    
                        public int PostId { get; set; }
                        public Post Post { get; set; }
                    
                        public string TagId { get; set; }
                        public Tag Tag { get; set; }
                    }
                    

                    来源:

                    https://docs.microsoft.com/en-us/ef/core/modeling/relationships?tabs=fluent-api%2Cfluent-api-simple-key%2Csimple-key#join-entity-type-configuration

                    https://docs.microsoft.com/en-us/dotnet/api/microsoft.entityframeworkcore.deletebehavior?view=efcore-5.0

                    这确实需要您自己删除多对多关系,否则在删除父实体时会收到以下错误:

                    实体类型 '' 和 '' 之间的关联已被切断,但是 该关系要么被标记为必需,要么被隐含 需要,因为外键不可为空。如果 当需要关系时,应删除依赖/子实体 被切断,配置关系使用级联删除。 考虑使用“DbContextOptionsBuilder.EnableSensitiveDataLogging”来 查看关键值

                    您可以通过使用 DeleteBehavior.ClientCascade 来解决此问题,这将允许 EF 对加载的实体执行级联删除。

                    internal class MyContext : DbContext
                    {
                        public MyContext(DbContextOptions<MyContext> options)
                            : base(options)
                        {
                        }
                    
                        public DbSet<Post> Posts { get; set; }
                        public DbSet<Tag> Tags { get; set; }
                    
                        protected override void OnModelCreating(ModelBuilder modelBuilder)
                        {
                            modelBuilder.Entity<Post>()
                                .HasMany(p => p.Tags)
                                .WithMany(p => p.Posts)
                                .UsingEntity<PostTag>(
                                    j => j
                                        .HasOne(pt => pt.Tag)
                                        .WithMany(t => t.PostTags)
                                        .HasForeignKey(pt => pt.TagId)
                                        .OnDelete(DeleteBehavior.Cascade),
                                    j => j
                                        .HasOne(pt => pt.Post)
                                        .WithMany(p => p.PostTags)
                                        .HasForeignKey(pt => pt.PostId)
                                        .OnDelete(DeleteBehavior.ClientCascade),
                                    j =>
                                    {
                                        j.Property(pt => pt.PublicationDate).HasDefaultValueSql("CURRENT_TIMESTAMP");
                                        j.HasKey(t => new { t.PostId, t.TagId });
                                    });
                        }
                    }
                    
                    public class Post
                    {
                        public int PostId { get; set; }
                        public string Title { get; set; }
                        public string Content { get; set; }
                    
                        public ICollection<Tag> Tags { get; set; }
                        public List<PostTag> PostTags { get; set; }
                    }
                    
                    public class Tag
                    {
                        public string TagId { get; set; }
                    
                        public ICollection<Post> Posts { get; set; }
                        public List<PostTag> PostTags { get; set; }
                    }
                    
                    public class PostTag
                    {
                        public DateTime PublicationDate { get; set; }
                    
                        public int PostId { get; set; }
                        public Post Post { get; set; }
                    
                        public string TagId { get; set; }
                        public Tag Tag { get; set; }
                    }
                    

                    https://docs.microsoft.com/en-us/dotnet/api/microsoft.entityframeworkcore.deletebehavior?view=efcore-5.0

                    【讨论】:

                      【解决方案19】:

                      您可以在 DataContext.cs 中添加它,这对我有用...

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

                      【讨论】:

                        猜你喜欢
                        • 2011-05-08
                        • 2013-10-22
                        相关资源
                        最近更新 更多