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

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

这是我的Card实体:

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


当前回答

在。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

其他回答

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

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

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

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

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

我也有这个问题,我立即解决了它的答案从一个类似的线程

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

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

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

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

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