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

jtjikinw  于 2022-09-18  发布在  Java
关注(0)|答案(19)|浏览(126)

我已经在这件事上挣扎了一段时间,不太明白发生了什么。我有一个卡片实体,它包含两个侧面(通常是2个)-并且卡片和侧面都有一个舞台。我正在使用EF CodeFirst迁移,迁移失败,错误如下:
在表‘side’上引入外键约束‘fk_dbo.sids_dbo.Cards_CardID’可能会导致循环或多个级联路径。指定ON DELETE NO ACTION或ON UPDATE NO ACTION,或修改其他外键约束。

这是我的卡片实体:

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

在我的边类中,我看到StageId列添加到我的Side表中。

这是有效的,但是现在在我的整个应用程序中,任何对Stage的引用都包含一个SideId,这在某些情况下是完全不相关的。我只想给我的CardSide实体一个基于上述Stage类的Stage属性,如果可能的话,不会污染带有引用属性的Stage类...我做错了什么?

qojgxg4l

qojgxg4l1#

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

然后我开始逐个remove-migration,直到包管理器控制台抛出我异常:
迁移‘20170827183131_*’已应用于数据库

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

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

xfyts7mz

xfyts7mz2#

我遇到了同样的问题,并坚持了很长时间。以下步骤救了我一命。浏览约束并将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);
  });
4jb9z9bj

4jb9z9bj3#

你可以把这个添加到你的DataConext.cs中,这对我来说很有效……

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

plicqrtu4#

简单的方法是,编辑您的迁移文件(cascadeDelete: true)(cascadeDelete: false),然后在您的包管理器控制台中分配更新数据库命令。如果上次迁移有问题,那么没关系。否则,检查您以前的迁移历史记录,复制这些内容,粘贴到您的最后一个迁移文件中,然后执行相同的操作。它非常适合我。

c9x0cxw0

c9x0cxw05#

前面提到的解决方案对我来说都不管用。我要做的是使用一个可为空的int(int?)在不需要的外键上(或者不是非空列键),然后删除我的一些迁移。

从删除迁移开始,然后尝试可以为空的int。

问题既是改装,也是模型设计。不需要更改代码。

epggiuax

epggiuax6#

在.NET 5<和.NET Core 2.0<中,您可以在OnModelCreating中使用.OnDelete(DeleteBehavior.Restrict) Like@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://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

这确实需要您自己删除多对多关系,否则在删除父实体时会收到以下错误:
实体类型‘’和‘’之间的关联已被切断,但由于外键不可为Null,该关系被标记为Required或被隐式要求。如果在断开所需关系时应删除从属/子实体,请将该关系配置为使用级联删除。考虑使用‘DbContextOptionsBuilder.EnableSensitiveDataLogging’查看密钥值

您可以通过使用DeleteBehavior.ClientCascade来解决此问题,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

kfgdxczn

kfgdxczn7#

现有的答案很好,我只是想补充说,我遇到这个错误是因为另一个原因。我想要在现有数据库上创建初始EF迁移,但我没有使用**-IgnoreChanges**标志,而是在空数据库上应用了更新数据库命令(也在现有的失败数据库上)。

相反,当当前的数据库结构是当前的数据库结构时,我必须运行以下命令:

Add-Migration Initial -IgnoreChanges

数据库结构可能存在真正的问题,但要一步一个脚印地拯救世界……

t2a7ltrp

t2a7ltrp8#

这听起来很奇怪,我不知道为什么,但在我的例子中,这是因为我的ConnectionString使用了“。在“数据源”属性中。一旦我将其更改为“本地主机”,它就像一个护身符一样工作。不需要其他改变。

oxf4rvwz

oxf4rvwz9#

使您的外键属性可为空。这会奏效的。

mklgxw1f

mklgxw1f10#

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‘上引入外键约束’FK_dbo.RecommendedBook_dbo.Department_DepartmentID‘可能会导致循环或多个级联路径。指定ON DELETE NO ACTION或ON UPDATE NO ACTION,或修改其他外键约束。无法创建约束或索引。请参阅前面的错误。‘

下面是一个使用‘Modify Other Foreign Key Constraints’的示例,方法是在迁移文件中将‘CascadeDelete’设置为FALSE,然后运行‘UPDATE-DATABASE’。

h7appiyu

h7appiyu11#

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

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

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

6uxekuva12#

我解决了这个问题。当您添加迁移时,在up()方法中将有如下一行:

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

如果您只需从末尾删除CascadeDelete,它就会起作用。

zzoitvuj

zzoitvuj13#

我也有这个问题,我立即用this answer from a similar thread解决了它

在我的例子中,我不想删除键删除时的从属记录。如果您的情况是这种情况,只需将迁移中的布尔值更改为False:

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

如果您正在创建抛出此编译器错误的关系,但确实希望维护级联删除,则可能是您的关系有问题。

egdjgwm8

egdjgwm814#

因为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);
suzh9iv8

suzh9iv815#

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

相关问题