十年河東,十年河西,莫欺少年窮
學無止境,精益求精
操作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
@天才臥龍的博科人