十年河东,十年河西,莫欺少年穷
学无止境,精益求精
操作EfCore时,数据迁移执行update-database时报如下错误
将 FOREIGN KEY 约束 'FK_S_Books_S_Companys_companyId' 引入表 'S_Books' 可能会导致循环或多重级联路径。请指定 ON DELETE NO ACTION 或 ON UPDATE NO ACTION,或修改其他 FOREIGN KEY 约束。 无法创建约束或索引。请参阅前面的错误。
怎么解决呢?
有这样一个需求,公司、公司内有多个员工,每个员工可以写多本书
using System; using System.Collections.Generic; using System.Text; namespace CoreDbContext.DbDtos { public class S_Company { public string uid { get; set; } public string companyName { get; set; } } public class S_Author { public string uid { get; set; } public string authorName { get; set; } public string companyId { get; set; } public virtual S_Company Company { get; set; } } public class S_Book { public string uid { get; set; } public string bookName { get; set; } public string authorId { get; set; } public virtual S_Author Author { get; set; } public string companyId { get; set; } public virtual S_Company Company { get; set; } } }
其配置关系如下:
using System; using System.Collections.Generic; using System.Text; using CoreDbContext.DbDtos; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Metadata.Builders; namespace CoreDbContext.DbConfigs { internal class S_CompanyConfig : IEntityTypeConfiguration<S_Company> { public void Configure(EntityTypeBuilder<S_Company> builder) { builder.ToTable("S_Companys"); builder.HasKey(A => A.uid); builder.Property(A => A.uid).HasColumnType("varchar(64)").HasComment("主键"); builder.Property(A => A.companyName).HasMaxLength(128).HasComment("公司名称"); } } internal class S_AuthorConfig : IEntityTypeConfiguration<S_Author> { public void Configure(EntityTypeBuilder<S_Author> builder) { builder.ToTable("S_Authors"); builder.HasKey(A => A.uid); builder.Property(A => A.uid).HasColumnType("varchar(64)").HasComment("主键"); builder.Property(A => A.companyId).HasColumnType("varchar(64)").HasComment("外键-所属公司"); builder.Property(A => A.authorName).HasMaxLength(32).HasComment("作者姓名"); //一对多配置 builder.HasOne<S_Company>(A => A.Company).WithMany().HasForeignKey(A => A.companyId); } } internal class S_BookConfig : IEntityTypeConfiguration<S_Book> { public void Configure(EntityTypeBuilder<S_Book> builder) { builder.ToTable("S_Books"); builder.HasKey(A => A.uid); builder.Property(A => A.uid).HasColumnType("varchar(64)").HasComment("主键"); builder.Property(A => A.companyId).HasColumnType("varchar(64)").HasComment("外键-所属公司"); builder.Property(A => A.authorId).HasColumnType("varchar(64)").HasComment("外键-作者"); builder.Property(A => A.bookName).HasMaxLength(32).HasComment("书名"); //一对多配置 builder.HasOne<S_Company>(A => A.Company).WithMany().HasForeignKey(A => A.companyId); builder.HasOne<S_Author>(A => A.Author).WithMany().HasForeignKey(A => A.authorId); } } }
此时,使用Add-Migration 和 Update-Database 命令做数据库迁移时,就会出现上述错误。解决这个错误之前,应先了解EfCore删除关联实体的7种策略,也称之为EfCore 级联删除规则,大家可自行百度,必应
级联删除:https://learn.microsoft.com/zh-cn/ef/core/saving/cascade-delete
EfCore 默然开启了级联删除,默认为:Cascade。
1、DeleteBehavior.Restrict
实际项目中使用较多的是软删除,因此,建议使用Restrict关闭级联删除。
框架不执行任何操作,由开发人员决定关联实体的行为
将efcore的默认策略改成自定义行为
protected override void OnModelCreating(ModelBuilder modelBuilder) { //关闭级联删除 var foreignKeys = modelBuilder.Model.GetEntityTypes().SelectMany(e => e.GetForeignKeys()).Where(fk => fk.DeleteBehavior == DeleteBehavior.Cascade); foreach (var fk in foreignKeys) { fk.DeleteBehavior = DeleteBehavior.Restrict; } //建立索引 并 增加唯一性约束 modelBuilder.Entity<T_CabinetProduce>().HasIndex(u => u.batchNo).IsUnique(); base.OnModelCreating(modelBuilder); //从当前程序集命名空间加载所有的IEntityTypeConfiguration modelBuilder.ApplyConfigurationsFromAssembly(this.GetType().Assembly); }
2、DeleteBehavior.SetNull
使用setNull时,外键和导航属性要设置为可为NULL,因此,实体变更如下
public class S_Company { public string uid { get; set; } public string companyName { get; set; } } public class S_Author { public string uid { get; set; } public string authorName { get; set; } public string? companyId { get; set; } public virtual S_Company? Company { get; set; } } public class S_Book { public string uid { get; set; } public string bookName { get; set; } public string? authorId { get; set; } public virtual S_Author? Author { get; set; } public string? companyId { get; set; } public virtual S_Company? Company { get; set; } }
关系配置时显式声明为SetNull
internal class S_CompanyConfig : IEntityTypeConfiguration<S_Company> { public void Configure(EntityTypeBuilder<S_Company> builder) { builder.ToTable("S_Companys"); builder.HasKey(A => A.uid); builder.Property(A => A.uid).HasColumnType("varchar(64)").HasComment("主键"); builder.Property(A => A.companyName).HasMaxLength(128).HasComment("公司名称"); } } internal class S_AuthorConfig : IEntityTypeConfiguration<S_Author> { public void Configure(EntityTypeBuilder<S_Author> builder) { builder.ToTable("S_Authors"); builder.HasKey(A => A.uid); builder.Property(A => A.uid).HasColumnType("varchar(64)").HasComment("主键"); builder.Property(A => A.companyId).HasColumnType("varchar(64)").HasComment("外键-所属公司"); builder.Property(A => A.authorName).HasMaxLength(32).HasComment("作者姓名"); //一对多配置 builder.HasOne<S_Company>(A => A.Company).WithMany().HasForeignKey(A => A.companyId).OnDelete(DeleteBehavior.SetNull); } } internal class S_BookConfig : IEntityTypeConfiguration<S_Book> { public void Configure(EntityTypeBuilder<S_Book> builder) { builder.ToTable("S_Books"); builder.HasKey(A => A.uid); builder.Property(A => A.uid).HasColumnType("varchar(64)").HasComment("主键"); builder.Property(A => A.companyId).HasColumnType("varchar(64)").HasComment("外键-所属公司"); builder.Property(A => A.authorId).HasColumnType("varchar(64)").HasComment("外键-作者"); builder.Property(A => A.bookName).HasMaxLength(32).HasComment("书名"); //一对多配置 builder.HasOne<S_Company>(A => A.Company).WithMany().HasForeignKey(A => A.companyId).OnDelete(DeleteBehavior.SetNull); builder.HasOne<S_Author>(A => A.Author).WithMany().HasForeignKey(A => A.authorId).OnDelete(DeleteBehavior.SetNull); } }
1.1、数据迁移
add-migration addtable -- update-database
插入测试数据
insert into [dbo].[S_Companys] values('001','江苏出版社') insert into [dbo].[S_Authors] values('author01','陈卧龙','001') insert into [dbo].[S_Authors] values('author02','陈大六','001') insert into [dbo].[S_Books] values('book01','平凡的世界','author01','001') insert into [dbo].[S_Books] values('book02','钢铁是怎样炼成的','author01','001') insert into [dbo].[S_Books] values('book03','心语','author02','001') insert into [dbo].[S_Books] values('book04','林语','author02','001') select *from [S_Companys] select *from [S_Authors] select *from [S_Books] delete from [S_Companys] delete from [S_Authors] delete from [S_Books]
1.2、测试
[AllowAnonymous] [HttpGet] [Route("Test")] public IActionResult Test() { var ef = context.Authors.Find("author02"); context.Authors.Remove(ef); context.SaveChanges(); return Ok(); }
上述代码删除其中名称为陈大六的作者,按照setNull的规定,当主表删除时,从表关联记录外键会被赋值为NULL
继续将公司删除
[AllowAnonymous] [HttpGet] [Route("Test")] public IActionResult Test() { var ef = context.Companys.Find("001"); context.Companys.Remove(ef); context.SaveChanges(); return Ok(); }
将公司删除,所有从表外键companyId均会被赋值为NULL
@天才卧龙的博科人