这个问题很容易复制,但我不知道解决它的正确方法。
例如,您有一个 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 约束。
我之前曾通过将 Team1ID 和 Team2ID 设为可为空来解决此问题。但是,这显然不是合适的解决方案。如果没有两个团队,游戏就不可能存在(在这种情况下......假设这是一场足球比赛)。此外,如果团队至少参加了一场比赛,则不应将其删除。
解决此问题的适当方法是什么?如果指定 ON DELETE NOT ACTION 或 ON UPDATE NO ACTION,或者修改其他 FOREIGN KEY 约束,该怎么做?
在 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 参考部分。
在 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 行为的方式。
或者为所有实体全局设置:
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/
就我而言,这是一种多对多的关系,这让我发疯, 尝试了一些方法,唯一有效的是将
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)
部分解决了错误