Appearance
迁移概述与工作流
EF Core 的数据库版本控制系统,完整指南
📖 目录
什么是迁移
概念理解
迁移(Migrations) 是 EF Core 的数据库版本控制系统,用于:
- 跟踪模型变化 - 实体类的修改
- 生成更新脚本 - SQL 变更语句
- 应用数据库变更 - 执行 SQL 脚本
- 版本回滚 - 恢复到之前的版本
代码模型变化 → 创建迁移 → 生成 SQL → 应用迁移 → 数据库更新为什么需要迁移
❌ 没有迁移的问题
csharp
// 1. 添加新属性
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
public string Description { get; set; } // 新增
}
// 问题:
// - 数据库表没有 Description 列
// - 运行时抛出异常
// - 需要手动修改数据库
// - 团队协作困难
// - 生产环境部署复杂✅ 使用迁移的优势
bash
# 1. 创建迁移
dotnet ef migrations add AddProductDescription
# 2. 应用迁移
dotnet ef database update
# 优势:
# ✅ 自动生成 SQL
# ✅ 版本控制
# ✅ 可重复执行
# ✅ 支持回滚
# ✅ 团队协作友好
# ✅ 生产环境安全迁移工作流
完整工作流程
mermaid
graph LR
A[修改实体类] --> B[创建迁移]
B --> C[审查迁移代码]
C --> D[测试迁移]
D --> E[提交到版本控制]
E --> F[应用到开发数据库]
F --> G[合并到主分支]
G --> H[应用到生产数据库]详细步骤
步骤 1: 修改模型
csharp
// Product.cs
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
// 新增字段
public string Description { get; set; }
public decimal Weight { get; set; }
// 修改字段
// public string Name { get; set; } → 增加长度限制
}步骤 2: 创建迁移
bash
dotnet ef migrations add AddProductDescriptionAndWeight命名规范:
- ✅
Add<Product>Description- 添加字段 - ✅
Remove<Product>Price- 删除字段 - ✅
Modify<Product>NameLength- 修改字段 - ✅
Create<Orders>Table- 创建表 - ❌
Migration1,Update- 不清晰
步骤 3: 审查生成的迁移
csharp
// Migrations/20240101120000_AddProductDescriptionAndWeight.cs
public partial class AddProductDescriptionAndWeight : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
// 添加 Description 列
migrationBuilder.AddColumn<string>(
name: "Description",
table: "Products",
type: "nvarchar(max)",
nullable: true);
// 添加 Weight 列
migrationBuilder.AddColumn<decimal>(
name: "Weight",
table: "Products",
type: "decimal(18,2)",
nullable: false,
defaultValue: 0m);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
// 回滚: 删除列
migrationBuilder.DropColumn(
name: "Description",
table: "Products");
migrationBuilder.DropColumn(
name: "Weight",
table: "Products");
}
}检查要点:
- ✅ 列类型是否正确
- ✅ 是否允许 NULL
- ✅ 默认值是否合理
- ✅ Down 方法是否正确
步骤 4: 应用到数据库
bash
# 应用到开发环境
dotnet ef database update
# 应用到指定迁移
dotnet ef database update AddProductDescriptionAndWeight
# 回滚到初始状态
dotnet ef database update 0步骤 5: 提交到版本控制
bash
git add Migrations/
git commit -m "Add Product Description and Weight fields"
git push注意: 迁移文件必须提交到 Git!
常用命令
CLI 命令(推荐)
bash
# 安装工具
dotnet tool install --global dotnet-ef
dotnet tool update --global dotnet-ef
# 创建迁移
dotnet ef migrations add <名称>
# 移除最后一次迁移
dotnet ef migrations remove
# 列出所有迁移
dotnet ef migrations list
# 应用迁移
dotnet ef database update
dotnet ef database update <迁移名称>
# 生成 SQL 脚本
dotnet ef migrations script
dotnet ef migrations script <from> <to>
# 查看 DbContext 信息
dotnet ef dbcontext info
# 逆向工程(Database First)
dotnet ef dbcontext scaffold "连接字符串" Microsoft.EntityFrameworkCore.SqlServerPMC 命令(Visual Studio)
powershell
# 创建迁移
Add-Migration <名称>
# 移除迁移
Remove-Migration
# 列出迁移
Get-Migration
# 应用迁移
Update-Database
Update-Database <迁移名称>
# 生成 SQL 脚本
Script-Migration
Script-Migration -From <迁移> -To <迁移>
# 删除数据库
Drop-Database迁移文件结构
文件组织
Migrations/
├── 20240101120000_InitialCreate.cs # 迁移文件
├── 20240101120000_InitialCreate.Designer.cs # 元数据
├── 20240101130000_AddDescription.cs # 迁移文件
├── 20240101130000_AddDescription.Designer.cs# 元数据
└── AppDbContextModelSnapshot.cs # 当前模型快照迁移文件详解
csharp
// 20240101120000_InitialCreate.cs
[DbContext(typeof(AppDbContext))]
[Migration("20240101120000_InitialCreate")]
public partial class InitialCreate : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
// 创建表
migrationBuilder.CreateTable(
name: "Categories",
columns: table => new
{
Id = table.Column<int>(type: "int", nullable: false)
.Annotation("SqlServer:Identity", "1, 1"),
Name = table.Column<string>(type: "nvarchar(50)", maxLength: 50, nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Categories", x => x.Id);
});
// 创建索引
migrationBuilder.CreateIndex(
name: "IX_Products_CategoryId",
table: "Products",
column: "CategoryId");
// 添加外键
migrationBuilder.AddForeignKey(
name: "FK_Products_Categories_CategoryId",
table: "Products",
column: "CategoryId",
principalTable: "Categories",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
// 删除外键
migrationBuilder.DropForeignKey(
name: "FK_Products_Categories_CategoryId",
table: "Products");
// 删除索引
migrationBuilder.DropIndex(
name: "IX_Products_CategoryId",
table: "Products");
// 删除表
migrationBuilder.DropTable(name: "Categories");
}
}Designer 文件
csharp
// 20240101120000_InitialCreate.Designer.cs
[DbContext(typeof(AppDbContext))]
partial class InitialCreate
{
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
// 构建目标模型
// 用于检测模型变化
}
}快照文件
csharp
// AppDbContextModelSnapshot.cs
[DbContext(typeof(AppDbContext))]
partial class AppDbContextModelSnapshot : ModelSnapshot
{
protected override void BuildModel(ModelBuilder modelBuilder)
{
// 当前完整的模型定义
// EF Core 用它对比检测变化
}
}注意:
- ⚠️ 不要手动编辑快照文件
- ⚠️ 每次迁移都会自动更新
最佳实践
1. 迁移命名规范
bash
# ✅ 好的命名
dotnet ef migrations add AddUserEmail
dotnet ef migrations add RemoveProductDiscount
dotnet ef migrations add ModifyOrderStatusLength
dotnet ef migrations add CreateAuditLogsTable
# ❌ 坏的命名
dotnet ef migrations add Migration1
dotnet ef migrations add Update
dotnet ef migrations add Changes2. 一次迁移一个变更
bash
# ✅ 推荐: 分开创建
dotnet ef migrations add AddUserEmail
dotnet ef migrations add AddUserProfilePicture
# ❌ 避免: 混合多个变更
dotnet ef migrations add MultipleChanges优点:
- ✅ 易于理解和审查
- ✅ 便于回滚特定变更
- ✅ 清晰的提交历史
3. 审查迁移代码
csharp
// 创建迁移后,立即审查
public partial class AddUserEmail : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
// 检查:
// □ 列类型是否正确?
// □ 是否允许 NULL?
// □ 是否有默认值?
// □ 是否需要索引?
migrationBuilder.AddColumn<string>(
name: "Email",
table: "Users",
type: "nvarchar(100)",
maxLength: 100,
nullable: false, // ⚠️ 确认是否允许 NULL
defaultValue: ""); // ⚠️ 确认默认值
}
}4. 测试迁移
bash
# 1. 在开发环境测试
dotnet ef database update
# 2. 验证功能正常
# 运行应用程序,测试新功能
# 3. 测试回滚
dotnet ef database update PreviousMigration
dotnet ef database update
# 4. 生成生产脚本
dotnet ef migrations script -o migration.sql5. 生产环境部署
方案 1: 应用迁移
bash
# 在生产服务器执行
dotnet ef database update优点: 简单
缺点: 需要安装 EF Core 工具
方案 2: SQL 脚本(推荐)
bash
# 生成 SQL 脚本
dotnet ef migrations script -o production.sql
# 在服务器上执行 SQL
sqlcmd -S server -d database -i production.sql优点:
- ✅ 无需安装工具
- ✅ DBA 可以审查
- ✅ 可以集成到部署流程
方案 3: 程序启动时自动应用
csharp
// Program.cs
using (var scope = app.Services.CreateScope())
{
var context = scope.ServiceProvider.GetRequiredService<AppDbContext>();
// 自动应用所有待处理的迁移
context.Database.Migrate();
}优点: 自动化
缺点:
- ⚠️ 需要适当权限
- ⚠️ 生产环境谨慎使用
6. 处理种子数据
csharp
protected override void Up(MigrationBuilder migrationBuilder)
{
// 插入种子数据
migrationBuilder.InsertData(
table: "Categories",
columns: new[] { "Id", "Name" },
values: new object[,]
{
{ 1, "Electronics" },
{ 2, "Books" },
{ 3, "Clothing" }
});
}
protected override void Down(MigrationBuilder migrationBuilder)
{
// 删除种子数据
migrationBuilder.DeleteData(
table: "Categories",
keyColumn: "Id",
keyValues: new object[] { 1, 2, 3 });
}7. 处理破坏性变更
csharp
// 场景: 重命名列 Name → Title
// ❌ 错误: 直接删除和创建会丢失数据
migrationBuilder.DropColumn("Name", "Products");
migrationBuilder.AddColumn<string>("Title", "Products", ...);
// ✅ 正确: 先复制数据
migrationBuilder.AddColumn<string>("Title", "Products", ...);
// 复制数据
migrationBuilder.Sql(@"
UPDATE Products
SET Title = Name
");
migrationBuilder.DropColumn("Name", "Products");常见问题
1. 迁移冲突
问题: 多人同时创建迁移
bash
# 解决方案 1: 重新同步
git pull
dotnet ef migrations remove
dotnet ef migrations add <新名称>
# 解决方案 2: 合并迁移
# 手动合并两个迁移文件2. 迁移失败
问题: 应用迁移时出错
bash
# 解决步骤:
# 1. 查看错误信息
dotnet ef database update
# 2. 回滚到上一个版本
dotnet ef database update PreviousMigration
# 3. 修复问题
# 修改迁移代码或模型
# 4. 重新应用
dotnet ef database update3. 删除迁移
bash
# 删除最后一次迁移
dotnet ef migrations remove
# 注意:
# ⚠️ 只能删除未应用的迁移
# ⚠️ 已应用的迁移需要先回滚4. 重置迁移
bash
# 完全重置(谨慎使用!)
dotnet ef database drop
dotnet ef migrations remove
dotnet ef migrations add InitialCreate
dotnet ef database update💡 小结
核心要点:
- ✅ 迁移是数据库版本控制系统
- ✅ 遵循工作流: 修改 → 创建 → 审查 → 测试 → 应用
- ✅ 使用清晰的命名规范
- ✅ 一次迁移一个变更
- ✅ 生产环境使用 SQL 脚本
- ✅ 始终审查生成的迁移代码
下一步: