引入 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
,在某些情况下这完全不相关。 如果可能的话,我只想给我的 Card
和 Side
实体一个 Stage
属性,而不用引用属性污染舞台类...我是什么做错了吗?
【问题讨论】:
通过允许引用中的空值来禁用级联删除...所以在Side
类中添加可空整数并删除[Required]
属性=> public int? CardId get; set;
在 EF Core 中,您应该使用 DeleteBehavior.Restrict
或 DeleteBehavior.SetNull
禁用级联删除。
接受的答案是唯一正确的答案。问题是:如果我想要 required 关系,如何防止循环级联路径。一个简单的映射指令就足够了。因此,不建议将关系设为可选,或者更糟糕的是,编辑生成的迁移文件(在 db 模型和概念模型之间引入差异),或者更糟糕的是,禁用所有级联删除。
【参考方案1】:
因为Stage
是必需的,所有涉及Stage
的一对多关系都将默认启用级联删除。这意味着,如果您删除 Stage
实体
Side
删除将直接级联到Card
,因为Card
和Side
具有必需的一对多关系,默认情况下再次启用级联删除,然后它将从Card
级联到Side
因此,您有两个从 Stage
到 Side
的级联删除路径 - 这会导致异常。
您必须在至少一个实体中使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 => x.Stage).WithMany().HasForeignKey(x => x.StageId).OnDelete(DeleteBehavior.Restrict);
@Biscuits 扩展方法随时间而变化,或者您在调用HasOne()
之前忘记了builder _ .Entity<TEntity>() _
...
@ViRuSTRiNiTy,我的 sn-p 已经 2 岁了。但是,我认为你是对的 - 现在它适用于你选择实施IEntityTypeConfiguration<T>
。我不记得那些日子看到builder.Entity<T>
方法,但我可能是错的。尽管如此,它们都可以工作:)【参考方案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 ReferentialAction 从 Cascade
更改为 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约束