EFCore中如何移除主外键关系

本文涉及的产品
RDS SQL Server Serverless,2-4RCU 50GB 3个月
推荐场景:
云数据库 RDS SQL Server,基础系列 2核4GB
简介: 目录EFCore中如何移除主外键关系场景介绍主外键关系的问题解决思路禁止级联关系的生成MicroFX.EntityFrameworkCore.RemoveForeignKey扩展EFCore中如何移除主外键关系场景介绍我用EFCore写了一个blog程序,我要通过写文章来分享自己的知识,我定义了一个Article用来存放文章信息,我还定义了一个Category用来存放文章的分类,Category与Article是一对的关系。

目录

EFCore中如何移除主外键关系

场景介绍

我用EFCore写了一个blog程序,我要通过写文章来分享自己的知识,我定义了一个Article用来存放文章信息,我还定义了一个Category用来存放文章的分类,CategoryArticle是一对的关系。我的代码实现如下:

Article

public class Article
{
    public int Id {get;set;}
    
    public int CategoryId {get;set;}
    
    //导航属性,efcore会自动创建主外键关系
    public Category Category {get;set;}
}

Category

public class Category
{
    public int Id {get;set;}
    
    //导航属性,efcore会自动创建主外键关系
    public List<Article> Articles { get; set; }
}

MyBlogDbContext

public class MyBlogDbContext:DbContext
{
    public MyBlogDbContext(DbContextOptions options):base(options)
    {}
    
    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);
        //Article
        var articleBuilder = modelBuilder.Entity<Article>();
        articleBuilder.ToTable("Article");
        articleBuilder.HasKey(article => article.Id);
        articleBuilder.HasIndex(article => article.CategoryId);
        
        //Category
        var categoryBuilder=modelBuilder.Entity<Category>();
        categoryBuilder.ToTable("Category");
        categoryBuilder.HasKey(category => category.Id);
    }
}

主外键关系的问题

  1. 当我想添加一片文章的时候,主外键要求我先添加这个文章的分类才允许我添加文章
  2. 当我想删除一个分类的时候,主要建会将我的文章也删除
  3. 总之,级联给我带来了很多烦恼

解决办法

  1. 修改数据,禁用级联功能
  2. 删除我们代码中的导航属性,阻止生成级联关系

以上两种办法都不是我想要的:

​ 我不想去操作数据库,因为我用了code first,ef会去操作数据库,所以我不想去修改数据库的级联功能(实际项目中我还是回去禁用数据库的级联关系)

​ 我也不想去删除导航属性,因为我想用ef core的Include功能。

解决思路

​ 在不修改数据的设置,也不删除导航属性的前提下实现禁用级联功能,我的做法是禁止级联关系的生成,可能你会说你这等于变相修改了sql,但是我确实没有写sql删除级联关系,也没有删除导航属性,总之,我的目的达到了,效果还不错。那么我是这么实现的?

禁止级联关系的生成

​ 我要做的是取翻看EFCore的代码,找到真正生成级联sql的地方然后重写,幸运的是我找到了,这个类就是SqlServerMigrationsSqlGenerator,我的实现如下:

CustomMigrationsSqlGeneratore

    public class CustomMigrationsSqlGeneratore : SqlServerMigrationsSqlGenerator
    {
        public CustomMigrationsSqlGeneratore( MigrationsSqlGeneratorDependencies dependencies,  IMigrationsAnnotationProvider migrationsAnnotations) : base(dependencies, migrationsAnnotations)
        {
        }
        //重写这个方法
        protected override void Generate(CreateTableOperation operation, IModel model, MigrationCommandListBuilder builder)
        {
            //删除级联关系
            RemoveForeignKeysHelper.ExecuForeignKeys(operation);
            base.Generate(operation, model, builder);
        }
    }

RemoveForeignKeysHelper

    public class RemoveForeignKeysHelper
    {
        //定义个全局变量,用来存储需要移除的级联属性
        internal static ConcurrentDictionary<string, List<string>> RemoveForeignKeys = new ConcurrentDictionary<string, List<string>>();

        public  static void ExecuForeignKeys(CreateTableOperation operation)
        {
            if (RemoveForeignKeys.TryGetValue(operation.Name, out List<string> columns))
            {
                operation.ForeignKeys
                    .Where(item => item.Columns.Intersect(columns).Count() > 0)
                    .ToList()
                    .ForEach(item => operation.ForeignKeys.Remove(item));
            }
        }
    }

EntityTypeBuilderExtensions

为EntityTypeBuilder添加扩展方法,通过扩展方法纪录那些需要被移除的级联关系

    public static class EntityTypeBuilderExtensions
    {
        public static EntityTypeBuilder<T> RemoeForeignKey<T>(this EntityTypeBuilder<T> builder,string name) where T : class
        {
            var tableName = builder.Metadata.FindAnnotation("Relational:TableName").Value.ToString();
            RemoveForeignKeysHelper.RemoveForeignKeys.AddOrUpdate(tableName, new List<string> { name },(value,values)=> {
                values.Add(name);
                return values.Distinct().ToList();
            });
            return builder;
        }
    }

DbContextOptionsBuilderExtensions

通过依赖注入,将生产sql的服务替换成我们自己的

    public static class DbContextOptionsBuilderExtensions
    {
        public static DbContextOptionsBuilder UseRemoveForeignKeyService(this DbContextOptionsBuilder options)
        {
            options.ReplaceService<IMigrationsSqlGenerator, CustomMigrationsSqlGeneratore>();
            return options;
        }
    }

到此,所有的代码都已经搞定,我们来看看怎么在我们的代码中引入这些功能。

首先,我们在创建Model的时候设置我要移除的级联关系,修改我们之前定义的MyBlogDbContext

MyBlogDbContext

public class MyBlogDbContext:DbContext
{
    public MyBlogDbContext(DbContextOptions options):base(options)
    {}
    
    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);
        //Article
        var articleBuilder = modelBuilder.Entity<Article>();
        articleBuilder.ToTable("Article");
        articleBuilder.HasKey(article => article.Id);
        articleBuilder.HasIndex(article => article.CategoryId);
        
        ///你没有看错就是这么顺滑
        articleBuilder.RemoeForeignKey("CategoryId");
        
        //Category
        var categoryBuilder=modelBuilder.Entity<Category>();
        categoryBuilder.ToTable("Category");
        categoryBuilder.HasKey(category => category.Id);
    }
}

然后,将IMigrationsSqlGenerator替换成我们自定义的类CustomMigrationsSqlGeneratore

//AddDbContxt记得吧,在Startup中或者在你自己扩展的IServiceCollection方法中
service.AddDbContext<MicroBlogDbContext>(options =>
            {
                //核心操作就在这里
                options.UseRemoveForeignKeyService();
                options.UseMySql(connStr,config=> {
                    config.CharSetBehavior(CharSetBehavior.AppendToAllColumns);
                    config.AnsiCharSet(CharSet.Latin1);
                    config.UnicodeCharSet(CharSet.Utf8mb4);
                });
            });

完!

MicroFX.EntityFrameworkCore.RemoveForeignKey扩展

​ 我写了个扩展,目前支持mysql和sqlserver,如果有机会我也会实现其它数据库的扩展。

MicroFX.EntityFrameworkCore.RemoveForeignKey

Micro.EntityFrameworkCore.RemoveForeignKey,SqlServer

Micro.EntityFrameworkCore.RemoveForeignKey.MySql

相关实践学习
使用SQL语句管理索引
本次实验主要介绍如何在RDS-SQLServer数据库中,使用SQL语句管理索引。
SQL Server on Linux入门教程
SQL Server数据库一直只提供Windows下的版本。2016年微软宣布推出可运行在Linux系统下的SQL Server数据库,该版本目前还是早期预览版本。本课程主要介绍SQLServer On Linux的基本知识。 相关的阿里云产品:云数据库RDS&nbsp;SQL Server版 RDS SQL Server不仅拥有高可用架构和任意时间点的数据恢复功能,强力支撑各种企业应用,同时也包含了微软的License费用,减少额外支出。 了解产品详情:&nbsp;https://www.aliyun.com/product/rds/sqlserver
目录
相关文章
|
6月前
|
存储 数据库 Python
视图函数中创建模型, 并设置外键
视图函数中创建模型, 并设置外键。
32 1
|
11月前
关联表更新封装
关联表更新封装
44 2
|
关系型数据库 MySQL 数据库
SQLAlchemy关联表一对多关系的详解
SQLAlchemy关联表一对多关系的详解
|
数据库 缓存 索引
关系数据库设计中,用中间表好还是直接设定主外键关联好?
有人会对数据库有这样的疑问: 因为大多数的数据库教程上都是告诉你关系数据库如何去创建1:1、1:N和N:M的数据库关系,但我发现很多开源产品中,并没有直接使用关系数据库的关系查询、关系删除等功能,而是直接在代码中对多个表的查询结果进行组合。
2445 0
|
Web App开发 API 数据库
零代码实现一对一表关系和无限主子表级联保存
本文主要介绍一对一关系和无限主子表在crudapi系统中的应用。一对一关系是指关系数据库中两个表之间的一种关系。关系数据库中第一个表中的单个行只可以与第二个表中的一个行相关,且第二个表中的一个行也只可以与第一个表中的一个行相关。
306 0
零代码实现一对一表关系和无限主子表级联保存
|
Java 数据库
不设外键:用程序来实现表之间的关联
Association 相信有很多同学都有使用过Hibernate框架的开发经验,尽管现在开发人员对Hibernate的褒贬不一,我们暂且不谈这个话题。
1755 1