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

我已经为这个问题纠结了一段时间,不太明白发生了什么。我有一个卡片实体,它包含边(通常是2)-卡片和边都有一个阶段。我使用EF Codefirst迁移和迁移失败与此错误:

< p >介绍FK_dbo.Sides_dbo外键约束。Cards_CardId” 表'Sides'可能会导致循环或多个级联路径。指定 DELETE NO ACTION或ON UPDATE NO ACTION,或修改其他外键 约束。< / p >

下面是我的实体:

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; }
}

下面是我的一边实体:

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,这在某些情况下是完全无关的。我只想基于上面的Stage类给我的__ABC2和__ABC3实体一个Stage属性,如果可能的话,不会用引用属性污染Stage类……我做错了什么?

321015 次浏览

因为Stage要求,所有涉及到Stage的一对多关系在默认情况下都将启用级联删除。它的意思是,如果你删除一个Stage实体

  • delete将直接级联到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);

我也有这个问题,我立即用这个答案来自一个类似的帖子解决了它

在我的例子中,我不想删除键删除相关记录。如果在你的情况下是这种情况,只需简单地将迁移中的布尔值更改为false:

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

机会是,如果你正在创建抛出这个编译器错误的关系,但想要维护级联删除;你的人际关系有问题。

可以将cascadeDelete设置为false或true(在migration Up()方法中)。这取决于你的需求。

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

我有一个与其他表有循环关系的表,我得到了同样的错误。结果是关于外键,它不是空的。如果键不是空的,则必须删除相关对象,而循环关系不允许这样做。使用可空外键。

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

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

当我从EF7模型迁移到EF6版本时,我得到了很多实体的这个错误。我不想一次逐个查看每个实体,所以我使用:

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

有人想知道如何在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.....
net核心中,我玩了所有上面的答案-但没有任何成功。 我在DB结构中做了很多更改,每次添加新的迁移尝试update-database,但收到相同的错误

然后我开始一个接一个地remove-migration,直到包管理控制台抛出异常:

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

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

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

在。net Core中,我将onDelete选项改为ReferencialAction。再

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

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

相反,当当前db结构为当前时,我不得不运行这个命令:

Add-Migration Initial -IgnoreChanges

在数据库结构中可能有一个真正的问题,但一次拯救世界一步…

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

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

问题是修改和模型设计。不需要修改代码。

我搞定了。当你添加迁移时,在Up()方法中会有这样一行:

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

如果您只是从末尾删除cascadeDelete,它就可以工作。

只是为了文档的目的,对于将来的人来说,这个问题可以这么简单解决,通过这个方法,你可以做一个禁用一次的方法,你可以正常访问你的方法

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

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

简单的方法是,编辑你的迁移文件(cascadeDelete: true)(cascadeDelete: false),然后在你的包管理器控制台中分配Update-Database命令。如果是你上次迁移的问题,那好吧。否则,检查您之前的迁移历史,复制这些内容,粘贴到您的最后一个迁移文件中,然后执行相同的操作。它非常适合我。

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");
}
}
当你的迁移失败时,你会得到几个选项: '引入外键约束'FK_dbo.RecommendedBook_dbo. '表“recommendations book”上的Department_DepartmentID'可能会导致循环或多个级联路径。指定ON DELETE NO ACTION或ON UPDATE NO ACTION,或修改其他外键约束。 无法创建约束或索引。'

.

下面是一个使用“修改其他外键约束”的例子,在迁移文件中设置“cascadeDelete”为false,然后运行“update-database”。

使你的外键属性为空。这是可行的。

我也遇到了同样的问题,而且卡了很长时间。下面的步骤拯救了我。 遍历约束并将onDelete ReferentialAction级联

更改为
  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);
});

在.NET 5 <.NET Core 2.0 <你可以在OnModelCreating中使用.OnDelete(DeleteBehavior.Restrict),比如@Nexus23 answer,但你不需要为每个模型禁用级联。

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

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://learn.microsoft.com/en-us/ef/core/modeling/relationships?tabs=fluent-api%2Cfluent-api-simple-key%2Csimple-key#join-entity-type-configuration

https://learn.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://learn.microsoft.com/en-us/dotnet/api/microsoft.entityframeworkcore.deletebehavior?view=efcore-5.0

你可以在你的DataContext.cs中添加这个,这对我来说很有用…

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