4. EF Core数据库迁移工程化
在真实项目中,数据库结构不会一次定型。实体类增加一个属性、调整一个索引、拆分一张表,都会让数据库与模型产生差异。EF Core 的迁移(Migration)机制把这种差异变成可版本化、可审查、可回放的代码文件,从而让数据库演进像代码提交一样可控。本节围绕迁移的完整生命周期展开:创建迁移、更新数据库、生成 SQL 脚本、回滚版本,并说明 CLI 与 PMC 两种工具链的对应关系。
迁移的本质:模型快照与差异脚本
EF Core 在首次创建迁移时,会在项目中生成一个 Migrations 文件夹,其中包含两类文件:以时间戳命名的迁移文件(如 20240512103000_InitialCreate.cs)和模型快照文件 ApplicationDbContextModelSnapshot.cs。迁移文件中的 Up() 方法描述如何把数据库推进到新版本,Down() 方法描述如何退回上一版本;快照则记录当前模型的完整状态,供下一次生成迁移时做差异比较。理解这一点很关键:迁移不是“执行 SQL 的脚本集合”,而是“模型演进的历史记录”。
因此,每次修改实体后,正确顺序是:先改实体与 DbContext 配置,再生成迁移,最后应用到数据库。若跳过生成迁移直接改数据库,快照就会与真实库脱节,后续迁移会基于错误基线计算差异。
CLI 与 PMC:两条等价工具链
EF Core 提供两套命令入口。CLI(dotnet ef)跨平台,适合 CI/CD 与终端操作;PMC(Package Manager Console)在 Visual Studio 内使用,适合交互式开发。二者能力基本对等,常用命令映射如下:
| 操作 | CLI 命令 | PMC 命令 |
|---|---|---|
| 创建迁移 | dotnet ef migrations add InitialCreate | Add-Migration InitialCreate |
| 更新数据库 | dotnet ef database update | Update-Database |
| 生成 SQL 脚本 | dotnet ef migrations script | Script-Migration |
| 回滚到指定迁移 | dotnet ef database update AddOrderIndex | Update-Database AddOrderIndex |
| 移除最后一个迁移 | dotnet ef migrations remove | Remove-Migration |
| 列出迁移 | dotnet ef migrations list | Get-Migration |
使用 CLI 前需安装工具:dotnet tool install --global dotnet-ef;PMC 则需在项目中引用 Microsoft.EntityFrameworkCore.Design 包。若解决方案含多个项目,CLI 需通过 --project 指定迁移所在项目、--startup-project 指定启动项目。
创建迁移与更新数据库
假设有一个订单系统,初始模型包含 Order 实体。首次生成迁移:
dotnet ef migrations add InitialCreate --project src/OrderApi --startup-project src/OrderApi
命令执行后,EF Core 会对比模型与快照,生成迁移文件。打开该文件可以看到 migrationBuilder.CreateTable(...) 等调用。此时数据库尚未改变,只是“生成了变更计划”。应用变更:
dotnet ef database update --project src/OrderApi --startup-project src/OrderApi
EF Core 会连接数据库,按顺序执行所有未应用的迁移,并在 __EFMigrationsHistory 表中记录已应用的迁移 ID。这张历史表是判断“当前数据库处于哪个版本”的唯一依据。若更新失败,EF Core 默认在事务中执行,多数数据库会整体回滚,但部分 DDL 在特定数据库上不支持事务,需要人工检查。
生成 SQL 脚本:面向生产环境的交付方式
生产环境通常不允许应用进程直接改库,而是由 DBA 审查 SQL 后执行。此时应使用脚本模式:
dotnet ef migrations script InitialCreate AddOrderIndex -o migrate.sql --idempotent
其中 InitialCreate 是起始迁移,AddOrderIndex 是目标迁移,省略起始则从空库开始,省略目标则生成到最新。--idempotent 会生成带条件判断的脚本,重复执行时自动跳过已应用的迁移,适合多环境部署。生成的脚本包含 CREATE TABLE、CREATE INDEX 以及向 __EFMigrationsHistory 插入记录的语句,可直接交给 DBA 审阅。
版本回滚:把数据库退回历史状态
回滚有两种含义。第一种是“数据库回滚”:把目标迁移名作为参数传给 database update,EF Core 会执行从当前版本到目标版本之间所有迁移的 Down() 方法。例如当前在 AddOrderIndex,要退回 InitialCreate:
dotnet ef database update InitialCreate
若要退回空库,使用 dotnet ef database update 0。第二种是“代码回滚”:如果迁移尚未应用到任何数据库,可用 dotnet ef migrations remove 删除最后一个迁移文件并同步快照;若已应用,则需先回滚数据库再移除,否则快照与库不一致。
Down() 中包含 DropColumn,该列数据会永久丢失。生产环境回滚前应先备份,并确认 Down() 逻辑确实可逆。对于不可逆变更(如删除列),建议在迁移中显式注释说明,或改用“新增列 + 数据迁移 + 后续清理”的渐进策略。
迁移工程化的关键实践
- 迁移文件纳入版本控制:迁移是代码的一部分,必须提交到 Git,禁止手工修改已提交的迁移文件。
- 命名体现意图:使用
AddOrderIndex、RenameCustomerTable这类语义化名称,而非Update1。 - 生产用脚本、开发用 update:开发环境直接
database update提效,生产环境走migrations script审查流程。 - 避免在迁移中写业务数据:种子数据用
HasData或独立数据迁移,保持结构变更与数据变更分离。 - CI 中校验迁移一致性:在流水线执行
dotnet ef migrations script,若生成失败说明模型与迁移不同步,应阻断合并。
下面这张图概括了迁移从模型变更到数据库版本状态的完整流转关系,包括创建、应用、脚本交付与回滚四条路径,以及 __EFMigrationsHistory 在其中的版本锚点作用。