zoukankan      html  css  js  c++  java
  • EFCore中如何移除主外键关系

    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

  • 相关阅读:
    火车头采集标题如何伪原创(附教程)
    神经网络的模型分为哪几类,神经网络训练模型描述
    CSS background-color 详细说明 ccs修改背景颜色
    三大神经网络模型,神经网络简单实例
    golang查看数据类型
    git根据某个commit创建分支
    RocketMQ在在CentOS7中使用systemctl方式启动
    CentOS7使用firewall-cmd打开关闭防火墙与端口
    1,6,18,30,75的规律php版本
    发现自己
  • 原文地址:https://www.cnblogs.com/guodf/p/9682201.html
Copyright © 2011-2022 走看看