Net6 EfCore数据库迁移时报: 可能会导致循环或多重级联路径。请指定 ON DELETE NO ACTION ...错误

十年河东,十年河西,莫欺少年穷

学无止境,精益求精

操作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; }
    }
}
View Code

其配置关系如下:

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

        }
    }
}
View Code

此时,使用Add-Migration 和 Update-Database 命令做数据库迁移时,就会出现上述错误。解决这个错误之前,应先了解EfCore删除关联实体的7种策略,也称之为EfCore 级联删除规则,大家可自行百度,必应

 微软官方文档地址为:https://learn.microsoft.com/zh-cn/dotnet/api/microsoft.entityframeworkcore.deletebehavior?view=efcore-6.0

 级联删除: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);
        }
View Code

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; }
    }
View Code

关系配置时显式声明为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);

        }
    }
View Code

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]
View Code

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

@天才卧龙的博科人

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章