如何通过编程实现EF Core Code First方案下的迁移自动生成?
摘要:迁移(Migrations)是个啥玩意?IT 界从来不缺造词人才,总喜欢造各种各样的词。之所以叫迁移,大概是因为使用它可以创建并在后期修订数据库。总之,说人话就是迁移可以生成一系列的 .NET 类,每个类代表一个修订版本。开发者可以在多个版
迁移(Migrations)是个啥玩意?IT 界从来不缺造词人才,总喜欢造各种各样的词。之所以叫迁移,大概是因为使用它可以创建并在后期修订数据库。总之,说人话就是迁移可以生成一系列的 .NET 类,每个类代表一个修订版本。开发者可以在多个版本之间“进”或“退”——可以修改数据库,之后可以撤销前一次修改。注意,这里说的修改 / 修订不是指数据,而是数据库的基础结构,比如,某个表后面由于某些原因,要添加一列,或要删除一列。
大伙伴都知道,调用 dbContext.Database.EnsureCreated 方法可以根据配置的 Model 创建数据库,它与迁移最大的区别就是:EnsureCreated 方法创建的数据库在后期是不能修改的(可以手动执行 SQL 语句来修改)。而迁移在创建数据库时它会顺便把当前迁移的版本信息保存到数据库(实体类 HistoryRow 类,包含两个属性:MigrationId 表示迁移ID,ProductVersion 表示 EF Core 版本),这样可以通过版本对比来确定版本的前进和回退,也可依此判定哪些迁移已应用到数据库,哪些还没同步到数据库。
为了能友好地分辨出迁移版本,在生成迁移代码时,开发者可以自定义一个命名。其格式是“<当前日期>_<自定义名称>”。例如“20251213102915_abc”,即开发者实际指定的命名是“abc”,前缀的时间和下画线是 EF Core 设计时服务自动加的。生成此名称是由IMigrationsIdGenerator 服务接口负责的,默认的实现类是MigrationsIdGenerator。咱们不妨看看GenerateId 方法的源码:
public virtual string GenerateId(string name)
{
var now = DateTime.UtcNow;
var timestamp = new DateTime(now.Year, now.Month, now.Day, now.Hour, now.Minute, now.Second);
……
// 这里拼接新名称
return timestamp.ToString(Format, CultureInfo.InvariantCulture) + "_" + name;
}
很简单粗暴吧,就是获取当前时间(精确到秒,足够了,你该不会每秒生成一次这么无聊吧),然后加上“_”字符,再加上你给的名字。有大伙伴会问,我要是不喜欢这种命名方式,我自己写个类实现IMigrationsIdGenerator 接口,注册到服务容器中替换到框架的默认实现,那是不是可以实现以自己喜欢的方式生成迁移命名呢?答案是肯定的。
迁移是由一系列 Operation 组成,用MigrationOperation 类表示。依据修改数据库的各种骚操作派生出相应的类。如AlterTableOperation 表,它代表 SQL 语句:ALTER TABLE ...;再比如AddColumnOperation 类,它代表ALTER TABLE <表> ADD <新列> 语句,SqlServerCreateDatabaseOperation 类代表CREATE DATABASE <数据库名> 语句,等等。这些类都能在Microsoft.EntityFrameworkCore.Migrations.Operations 命名空间下找到。
在迁移的时候,会根据这些 Operation 生成关联的 SQL 语句。例如,下面源码是生成添加新列的 SQL。
