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

Posted

技术标签:

【中文标题】引入 FOREIGN KEY 约束可能会导致循环或多个级联路径 - 为啥?【英文标题】: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 模型和概念模型之间引入差异),或者更糟糕的是,禁用所有级联删除。 【参考方案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 中不存在那些,那里有任何等价物吗? @jjxtra 检查***.com/questions/46526230/…【参考方案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>();

【讨论】:

以上是关于引入 FOREIGN KEY 约束可能会导致循环或多个级联路径 - 为啥?的主要内容,如果未能解决你的问题,请参考以下文章

Entity Framework Core 5 - 在表上引入 FOREIGN KEY 约束可能会导致循环或多个级联路径

在表“ReservedSeats”上引入 FOREIGN KEY 约束“FK_ReservedSeats_Seats_SeatId”可能会导致循环或多个级联路径

ASP.Net MVC 3 EF“在表上引入 FOREIGN KEY 约束可能会导致循环或多个级联路径”

在表“tblMaintenance”上引入 FOREIGN KEY 约束“FK2CustomerId”可能会导致循环或多个级联路径

SQL Server 引入 FOREIGN KEY 约束可能导致循环或多个级联路径

可能会导致循环或多个级联路径。指定ON DELETE NO ACTION或ON UPDATE NO ACTION,或修改其他FOREIGN KEY约束