• EntityFrameworkCore 模型自动更新(上)


    话题

    嗯,距离上一次写博文已经过去近整整十个月,还是有一些思考,但还是变得懒惰了,心思也不再那么专注,有点耗费时间,学习也有点停滞不前,那就顺其自然,随心所欲吧,等哪天心血来潮,想写了就写写

    模型自动更新方案研究(上)

    一般团队人数很少时,使用EF Core内置迁移基本已满足,满足的基本前提首先要生成迁移文件,然后和数据库进行对比,但团队人数一多,迁移文件等等涉及提交冲突等等,所以大部分情况下,我个人认为EF Core迁移基本没啥用,这玩意用不起来,尤其涉及到版本分支很多情况下,切换不同分支所使用的数据可能也会不同,我们常用MySql数据库,同时适配了人大金仓数据库、高斯数据库(GaussDb for opengauss),其对应的表结构有一些差异性,列类型也会有很大差异性,这对开发人员和测试人员来讲就是深深的折磨和痛苦,大部分时间会花在保持数据库表结构和模型一致,否则要么运行不起来,要么测试功能各种有问题,所以想想通过自动化方式来解决这个问题,本文还是分上下两篇写好了。

     

    那么解决此问题的思路是怎样的呢?同时适配多套数据库,重写一套?那是不阔能的,既然我们可以通过dotnet ef命令来进行迁移,通过和数据库表结构进行对比,从而生成迁移文件,迁移文件包含即将要执行的差异性脚本,从这个角度来看的话,我们从迁移类反堆即可得到生成的脚本以及和数据库进行对比操作方法,初步设想理论上应该行得通,只需花时间了解下源码就好,通过前期两天的啃源码,最终啃出百把行代码即可自动更新模型到数据库,当然这个过程中还涉及一些要考虑的细节,我们一一再叙。接下来我们以MySql为例讲讲整个过程,其他数据库依葫芦画瓢就好(比如使用SqlServer,将对应后缀MySql替换为SqlServer就好,同理对于Npgsql等等亦是如此),首先甩出如下代码:

    复制代码
    var services = new ServiceCollection();
    
    services.AddEntityFrameworkMySql();
    
    services.AddEntityFrameworkDesignTimeServices();
    
    services.AddDbContext((serviceProvider, options) =>
    {
        options.UseInternalServiceProvider(serviceProvider);
    
        options.UseMySql("server=localhost;Port=3306;Database=test;Username=root;Password=root;",ServerVersion.AutoDetect("server=localhost;Port=3306;Database=test;Username=root;Password=root;"));
    });
    
    services.AddScoped();
    
    var serviceProvider = services.BuildServiceProvider();
    复制代码

    EF Core有属于它自己的容器,所以我们将全局容器和上下文所属容器做了区分,同时呢,我们将迁移中要用到的操作依赖也手动添加,比如上面的设计服务,存在于 Microsoft.EntityFrameworkCore.Design 包内,最后将获取数据库表结构模型工厂手动注入即MySqlDatabaseModelFactory。接下来我们要获取模型定义以及属性一些定义等等,也就是我们最终要生成的目标模型结构

    复制代码
    using var scope = _serviceProvider.CreateScope();
    
    var currentServiceProvider = scope.ServiceProvider;
    
    var context = (DbContext)currentServiceProvider.GetRequiredService();
    
    var connectionString = context.Database.GetDbConnection().ConnectionString;
    
    var targetModel = context.GetService().Model.GetRelationalModel();
    
    if (targetModel == null)
    {
        return Enumerable.Empty().ToList();
    }
    复制代码

    接下来则是获取数据库表结构也就是数据库模型

    复制代码
    var databaseFactory = currentServiceProvider.GetService();
    
    var factory = currentServiceProvider.GetService();
    
    var tables = context.Model.GetEntityTypes().Select(e => e.GetTableName()).ToList();
    
    if (!tables.Any())
    {
        return Enumerable.Empty().ToList();
    }
    
    // 仅查询当前上下文模型所映射表,否则比对数据库表差异时,将会删除非当前上下文所有表
    var databaseModel = databaseFactory.Create(connectionString, new DatabaseModelFactoryOptions(tables: tables));
    
    if (databaseModel == null)
    {
        return Enumerable.Empty().ToList();
    }
    复制代码

    这里稍微需要注意的是,若是有多个不同上下文,肯定只查询当前上下文所对应的模型结构,不然最后生成的脚本会将其他上下文对应的表结构给删除。紧接着,我们需要将数据库模型转换为上下文中的模型,即类型一致转换,这就演变成了我们的源模型

    复制代码
    var model = factory.Create(databaseModel, new ModelReverseEngineerOptions());
    
    if (model == null)
    {
        return Enumerable.Empty().ToList();
    }
    
    var soureModel = model.GetRelationalModel();
    复制代码

    接下来自热而然就进行源模型和目标模型差异性比对,得到实际要进行的迁移操作

    复制代码
     var soureModel = model.GetRelationalModel();
    
    //TODO Compare sourceModel vs targetModel
    
    var modelDiffer = context.GetService();
    
    var migrationOperations = modelDiffer.GetDifferences(soureModel, targetModel);
    复制代码

    那接下来问题来了,拿到差异性迁移操作后,我们应该怎么处理呢?留着各位思考下,我们下篇见

    总结

    本文给出了自动更新模型思路以及整个完整实现逻辑,剩余内容我们下篇再叙,主要是没心情写,写不下去了,今天我们就到此为止~


    为了方便大家在移动端也能看到我分享的博文,现已注册个人公众号,扫描上方左边二维码即可,欢迎大家关注,有时间会及时分享相关技术博文。

    感谢花时间阅读此篇文章,如果您觉得这篇文章你学到了东西也是为了犒劳下博主的码字不易不妨打赏一下吧,让楼主能喝上一杯咖啡,在此谢过了!
    如果您觉得阅读本文对您有帮助,请点一下“推荐”按钮,您的“推荐”将是我最大的写作动力!
    本文版权归作者和博客园共有,来源网址:http://www.cnblogs.com/CreateMyself)/欢迎各位转载,但是未经作者本人同意,转载文章之后必须在文章页面明显位置给出作者和原文连接,否则保留追究法律责任的权利。
  • 相关阅读:
    Web框架开发-web框架
    我的UI自动化测试的感悟
    PixelSNAIL论文代码学习(1)——总体框架和平移实现因果卷积
    node.js室内装修风格选择系统毕业设计-附源码211552
    iOS Crash 治理:淘宝VisionKitCore 问题修复
    Android学习笔记 1.2.7 自定义任务 && 1.2.8 自定义插件
    改element的单选框的样式,改成方形有勾的样式
    3.11-程序基本的控制语句 3.12-表达式 3.13-数据类型 3.14-常量/变量 3.15-标识符
    算法金 | Python 中有没有所谓的 main 函数?为什么?
    Linux的OpenLava配置
  • 原文地址:https://www.cnblogs.com/CreateMyself/p/16667716.html