EFCore数据库迁移

1. 数据库迁移原理

数据库迁移的使用看似很简单,但是内部实现非常复杂,只要了解它的内部实现原理,我们才能更好地使用它。

Migrantions文件夹下的内容都是数据库迁移生成的代码,这些代码记录了对数据库的修改操作,一般情况下我们无需手工修改这些代码。

这些代码由两部分组成,一部分是以数字开头的文件,每一个文件代表一次对数据库的修改操作;

另一部分是ModelSnapshot文件,它是当前状态的快照。

我们注意到,每次执行Add-Migration之后,Migrations文件夹下都会生成两个文件,一个文件的名字为”数字_迁移名字.cs”,另一个文件的名字为”数字_迁移名字.Designer.cs”,我们把每一次执行Add-Migration称作一次”迁移”。

这些以数字开头的一组文件就对应了一次迁移,这些迁移开头的数字就是迁移的版本号,这些版本号是递增的,因此我们根据版本号对其进行排序就能得知数据库迁移的历史。

使用迁移脚本,我们可以对当前连接的数据库执行版本号更高的迁移,这个操作叫作”向上迁移”,我们也可以执行把数据库回退到旧版本的迁移,这个操作叫”向下迁移”。

假设项目中依次有版本号为1001、1002、1003、1004、1005的5个迁移,而当前连接的数据库已经完成的迁移版本号是1003,那么在当前数据库上执行1004这个脚本以后,就完成了向上迁移;

我们也可以把当前的数据库回退到1002这个版本,这样就完成了向下迁移。

由于EF Core记录了全部的历史版本信息,因此我们还可以连续迁移,比如可以在当前迁移版本号为1003的数据库上向下迁移到1002、再向下迁移到1001。

正因为如此,除非有特殊需要,否则我们不要删除Migrations文件夹下的代码。

接下来,再详细看看每一组迁移中两个文件的作用。

以demo1为例,20240414142554_demo1.cs中记录的是和具体数据库无关的抽象模型,而20240414142554_demo1.Designer.cs记录的是和具体数据库相关的代码。

C#
 public partial class demo1 : Migration
 {
     /// <inheritdoc />
     protected override void Up(MigrationBuilder migrationBuilder)
     {
         migrationBuilder.CreateTable("Authors", 
             tb => new {id = tb.Column<Guid>(),
             Name= tb.Column<string>() },
          constraints: tb => tb.PrimaryKey("PK_Authors",x=>x.id));
     }

     /// <inheritdoc />
     protected override void Down(MigrationBuilder migrationBuilder)
     {
         migrationBuilder.DropTable("Authors");
     }
 }

demo1类中包含Up和Down两个方法,Up方法中定义的是向上迁移的代码,也就是把上一个版本的数据库迁移到这个版本要执行的代码,而Down方法中定义的则是向下迁移的代码,也就是把这个版本的数据库迁移回上一个旧版本的代码。

这些代码都是由迁移工具生成的,一般不需要编写或者修改这些代码,但是为了研究EF Core的原理,我们还是有必要看懂它的大概逻辑。

可以看到,Up方法中,我们调用CreateTable方法创建了Authors表,并且定义了和实体类中对应的列,而Down方法中则调用DropTable方法把Authors表删除。

当我们在上一个版本的数据库中执行这个迁移脚本的时候,Up方法被执行,因此Authors表被创建;

当我们需要回退到上一个版本的数据库的时候,Down方法就会被执行,因此Authors表被删除。

因为Up方法是Down方法的”撤销操作”,所以这两个方法的代码需要实现完全相反的操作,也就是Down方法中的代码应该恰好把Up方法中对数据库的操作完全撤销,既不缺少一些操作,也不多出额外的操作。

20240414142554_demo1.Designer.cs文件中定义的也是和20240414142554_demo1.cs中相同的demo1类,它们两个通过部分类的语法各自组成demo1类的一部分。

20240414142554_demo1.Designer.cs的主干内容
C#
[DbContext(typeof(BookDbContext))]
[Migration("20240414142554_demo1")]
partial class demo1

demo1类上添加的[DbContext(typeof(BookDbContext))]表示这个迁移脚本应用于哪一个上下文,而[Migration(“20240414142554_demo1”)]代表这个迁移脚本的版本号。

我们再查看一下数据库就会发现数据库中有一个__EFMigrationsHistory表,表中的数据如下:

可以看到,__EFMigrationsHistory表中记录的就是当前数据库曾经应用过的迁移脚本,是按照顺序排列的,最后一条数据就是数据库最后一次应用的迁移版本号。

EF Core就是基于这张表得知当前连接的数据库的迁移版本号的,因此除非有特殊需要,否则不要修改这张表及其数据。

由于数据库迁移工具需要调用代码编译后的DLL文件去执行数据库迁移逻辑,因此在运行数据库迁移命令的时候,迁移工具会先尝试构建项目,如果项目构建失败,则迁移工作不能继续执行。

如果项目代码中有语法错误等会导致构建失败的代码,在执行Add-Migration等命令的时候,迁移工具就会提示”Build failed”的错误信息。

如果解决方案中有多个项目,在执行Add-Migration等命令的时候,一定要确认在【程序包管理器控制台】中选中的是要迁移的项目。

2. 其他数据库迁移命令

除了Add-migration、Update-database这两个常用命令之外,EF Core还提供了其他一些数据库迁移命令。

这些命令被使用的机会相对来讲比较少,这里只介绍常用的功能。

2.1 Update-database其他参数

我们可以用Update-database XXX把数据库回滚到XXX迁移脚本之后的状态。

注意,这个命令只把当前连接的数据库进行回滚,因此迁移脚本仍然存在。

2.2 删除迁移脚本

可以用Remove-migration命令删除最后一次的迁移脚本。

2.3 生成迁移脚本

我们可以用Update-database命令执行迁移脚本来自动修改数据库,但是这种方式只适合在开发环境下使用,而不能用于生产环境。

因为基于安全考虑,很多公司要求对生产环境数据库的操作必须要经过审计,而EF Core的迁移代码是一个二进制的程序,很难满足审计的要求;

而且大部分公司的开发环境并不能直接连接生产环境数据库。

EF Core中提供了Script-Migration命令来根据迁移代码生成SQL脚本,比如在【程序包管理器控制台】中输入Script-Migration并执行,一个包含完整的数据库操作脚本的SQL文件就会被创建和打开。

这个脚本可以被提交给相关人员审计,然后在生产数据库中执行。

如果生产数据库已经处于某个迁移版本的状态了,那么我们可以生成这个版本到某个新版本的SQL脚本。

比如当前数据库的前一版本是D,通过如下命令可以生成版本D到版本F的SQL脚本:Scripy-Migration D F

在EF Core中,我们还可以使用context.Database.Migrate()代码来对程序当前连接的数据库进行迁移。

这种方式是直接在代码中完成数据库迁移,很多公司的安全审计要求提供的是明文的SQL语句,因此我们需要根据公司安全审计要求决定是采用生成迁移SQL脚本的方式,还是通过迁移程序的方式执行数据库迁移。

订阅评论
提醒
0 评论
最旧
最新 最多投票
内联反馈
查看所有评论
滚动至顶部