指定 ON DELETE NO ACTION 或 ON UPDATE NO ACTION,或修改其他 FOREIGN KEY 约束。 - 怎么办?

问题描述 投票:0回答:4

这个问题很容易复制,但我不知道解决它的正确方法。

例如,您有一个 Team 类和一个 Game 类。每场比赛有两支球队。使用标准 OOTB EF 命名约定时,运行 dotnet ef 数据库更新时,您将遇到以下错误(dotnet ef 迁移 add 将运行而不会出现错误)。

课程:

public class Team
{
    [Required]
    public int TeamID { get; set; }
    public string TeamName { get; set; }
}

public class Game
{
    [Required]
    public int GameID { get; set; }
    public int Team1ID { get; set; }
    public Team Team1 { get; set; }
    public int Team2ID { get; set; }
    public Team Team2 { get; set; }
}

确保将这两个类添加到您的 DbContext 中:

public virtual DbSet<Team> Teams { get; set; }
public virtual DbSet<Game> Games { get; set; }

我收到的错误是:

在表“Games”上引入外键约束“FK_Games_Teams_Team2ID”可能会导致循环或多个级联路径。指定 ON DELETE NO ACTION 或 ON UPDATE NO ACTION,或修改其他 FOREIGN KEY 约束。

我之前曾通过将 Team1IDTeam2ID 设为可为空来解决此问题。但是,这显然不是合适的解决方案。如果没有两个团队,游戏就不可能存在(在这种情况下......假设这是一场足球比赛)。此外,如果团队至少参加了一场比赛,则不应将其删除。

解决此问题的适当方法是什么?如果指定 ON DELETE NOT ACTION 或 ON UPDATE NO ACTION,或者修改其他 FOREIGN KEY 约束,该怎么做?

c# entity-framework entity-framework-core
4个回答
30
投票

在 EF Core 中,关系的级联行为是通过 OnDelete 关系 Fluent API 配置的(默认情况下,它是

Cascade
,用于像您一样的所需关系)。

棘手的部分是如何访问该 API,因为没有直接的方法(例如,像

modelBuilder.Relation<TPrincipal, TDependent>()...
这样的东西本来就很好,但这样的 API 不存在),所以至少你需要从实体开始键入构建器,然后是正确的
Has{One|Many}
/
With{One|Many}
对。我所说的正确是指在存在时传递相应的导航属性。如果不这样做,将导致意外的附加关系/FK,因为 EF Core 会将未映射的导航属性映射到默认的常规关系/FK。

在你的情况下,它会是这样的:

modelBuilder.Entity<Game>()
    .HasOne(e => e.Team1)
    .WithMany();

modelBuilder.Entity<Game>()
    .HasOne(e => e.Team2)
    .WithMany();

现在您可以配置级联行为、FK 属性/约束名称等。

在这种特殊情况下,只需为两个关系插入

.OnDelete(DeleteBehavior.Restrict)
即可完成:

modelBuilder.Entity<Game>()
    .HasOne(e => e.Team1)
    .WithMany()
    .OnDelete(DeleteBehavior.Restrict); // <--

modelBuilder.Entity<Game>()
    .HasOne(e => e.Team2)
    .WithMany()
    .OnDelete(DeleteBehavior.Restrict); // <--

有关详细信息,请参阅文档的关系EF Core API 参考部分。


2
投票

在 DbContext 类上,您需要添加:

protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            modelBuilder.Entity<Game>(entity =>
            {
                entity.Property(e => e.GameId)
                    .HasColumnName("GameID");

                entity.Property(e => e.Team1Id).HasColumnName("Team1ID");

                entity.Property(e => e.Team2Id).HasColumnName("Team2ID");

                entity.HasOne(d => d.Team1)
                    .WithMany(p => p.GameTeam1)
                    .HasForeignKey(d => d.Team1Id)
                    .OnDelete(DeleteBehavior.ClientNoAction)
                    .HasConstraintName("FK_Games_Teams_Team1ID");

                entity.HasOne(d => d.Team2)
                    .WithMany(p => p.GameTeam2)
                    .HasForeignKey(d => d.Team2Id)
                    .OnDelete(DeleteBehavior.ClientNoAction)
                    .HasConstraintName("FK_Games_Teams_Team2ID");
            });

            modelBuilder.Entity<Team>(entity =>
            {
                entity.Property(e => e.TeamId)
                    .HasColumnName("TeamID")
                    .ValueGeneratedNever();

                entity.Property(e => e.TeamName)
                    .IsRequired();
            });

            OnModelCreatingPartial(modelBuilder);
        }

这整件事并不能完全代表您所需要的,但这就是您设置 ondelete 和 onupdate 行为的方式。


2
投票

或者为所有实体全局设置:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    if (modelBuilder == null)
        throw new ArgumentNullException("modelBuilder");

    // for the other conventions, we do a metadata model loop
    foreach (var entityType in modelBuilder.Model.GetEntityTypes())
    {
        // equivalent of modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
        entityType.SetTableName(entityType.DisplayName());

        // equivalent of modelBuilder.Conventions.Remove<OneToManyCascadeDeleteConvention>();
        entityType.GetForeignKeys()
            .Where(fk => !fk.IsOwnership && fk.DeleteBehavior == DeleteBehavior.Cascade)
            .ToList()
            .ForEach(fk => fk.DeleteBehavior = DeleteBehavior.Restrict);
    }

    base.OnModelCreating(modelBuilder);
}

Dennes Torres 的解决方案,来源:https://www.red-gate.com/simple-talk/blogs/change-delete-behavior-and-more-on-ef-core/


0
投票

就我而言,这是一种多对多的关系,这让我发疯, 尝试了一些方法,唯一有效的是将

DeleteBehavior
设置为
ClientCascade

public class Invoices
{
    public int Id { get; set; }
    //(few other parameters)
    public ICollection<Products> products { get; set; }
}

public class Products
{
    public int Id { get; set; }
    //(few other parameters)
    public ICollection<Invoices> invoices { get; set; }
}

public class InvoiceProduct
{
    public int InvoiceId { get; set; }
    public int ProductId { get; set; }
}

这些是我最基本形式的课程,它给了我关于

ON DELETE CASCADE
的长错误 为了修复它,我在 DbContext 的 OnModelCreating 重写方法中添加了以下代码:

protected override void OnModelCreating(ModelBuilder modelBuilder)
    {

        modelBuilder.Entity<Products>()
            .HasMany(e => e.invoices)
            .WithMany(e => e.products)
            .UsingEntity<InvoiceProduct>(
                r => r.HasOne<Invoices>().WithMany().HasForeignKey(e => e.InvoiceId).OnDelete(DeleteBehavior.ClientCascade),
                l => l.HasOne<Products>().WithMany().HasForeignKey(e => e.ProductId).OnDelete(DeleteBehavior.ClientCascade));
    }

.OnDelete(DeleteBehavior.ClientCascade)
部分解决了错误

© www.soinside.com 2019 - 2024. All rights reserved.