Appearance
迁移文件管理
在团队协作和长期项目中,EF Core 迁移文件的管理变得至关重要。本章将深入探讨迁移文件的组织、命名规范、冲突解决、版本控制和自动化部署策略。
目录
1. 迁移文件结构解析
1.1 迁移文件组成
每个 EF Core 迁移由三个文件组成:
Migrations/
├── 20240101120000_InitialCreate.cs // 主迁移类
├── 20240101120000_InitialCreate.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: "Products",
columns: table => new
{
Id = table.Column<int>(type: "int", nullable: false)
.Annotation("SqlServer:Identity", "1, 1"),
Name = table.Column<string>(type: "nvarchar(200)", nullable: false),
Price = table.Column<decimal>(type: "decimal(18,2)", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Products", x => x.Id);
});
migrationBuilder.CreateIndex(
name: "IX_Products_Name",
table: "Products",
column: "Name");
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(name: "Products");
}
}Designer 文件(自动生成,不应手动修改):
csharp
// 文件名: 20240101120000_InitialCreate.Designer.cs
[DbContext(typeof(AppDbContext))]
[Migration("20240101120000_InitialCreate")]
partial class InitialCreate
{
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder.Entity("Product", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int")
.HasAnnotation("SqlServer:ValueGenerationStrategy",
SqlServerValueGenerationStrategy.IdentityColumn);
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(200)
.HasColumnType("nvarchar(200)");
b.Property<decimal>("Price")
.HasColumnType("decimal(18,2)");
b.HasKey("Id");
b.ToTable("Products");
});
#pragma warning restore 612, 618
}
}模型快照文件(整个数据库的最终状态):
csharp
// 文件名: AppDbContextModelSnapshot.cs
[DbContext(typeof(AppDbContext))]
partial class AppDbContextModelSnapshot : ModelSnapshot
{
protected override void BuildModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
// 包含所有实体的最终模型状态
modelBuilder.Entity("Product", b => { /* ... */ });
modelBuilder.Entity("Order", b => { /* ... */ });
modelBuilder.Entity("Customer", b => { /* ... */ });
#pragma warning restore 612, 618
}
}1.2 迁移文件元数据
MigrationAttribute 解析:
csharp
// 属性定义
[AttributeUsage(AttributeTargets.Class)]
public class MigrationAttribute : Attribute
{
public string Id { get; } // 唯一标识符(时间戳)
public string Name { get; } // 迁移名称
public MigrationAttribute(string idAndName)
{
var parts = idAndName.Split('_', 2);
Id = parts[0]; // "20240101120000"
Name = parts.Length > 1 ? parts[1] : ""; // "InitialCreate"
}
}迁移 ID 生成规则:
csharp
// EF Core 内部实现(简化版)
public static class MigrationIdGenerator
{
public static string GenerateId(DateTime timestamp)
{
// 格式: yyyyMMddHHmmss (UTC 时间)
return timestamp.ToUniversalTime().ToString("yyyyMMddHHmmss");
}
}
// 示例:
// 2024-01-01 12:00:00 UTC → "20240101120000"
// 2024-06-15 09:30:45 UTC → "20240615093045"2. 迁移文件命名规范
2.1 自动命名 vs 手动命名
命令行创建迁移:
bash
# 方式1: 自动使用时间戳(推荐)
dotnet ef migrations add AddProductCategory
# 生成的文件:
# 20240115103000_AddProductCategory.cs
# 方式2: 自定义前缀(不推荐,容易混乱)
dotnet ef migrations add 20240115_CustomPrefix
# 生成的文件:
# 20240115103000_20240115_CustomPrefix.cs # 重复的时间戳!2.2 命名最佳实践
✅ 推荐的命名模式:
bash
# 1. 动词 + 名词 (清晰表达意图)
dotnet ef migrations add AddProductCategory
dotnet ef migrations add RemoveCustomerPhone
dotnet ef migrations add RenameOrderTotal
dotnet ef migrations add SplitCustomerAddress
# 2. 使用 PascalCase (与 C# 类名一致)
dotnet ef migrations add CreateOrdersTable # ✅ 好
dotnet ef migrations add create_orders_table # ❌ 避免
# 3. 保持简洁但描述性
dotnet ef migrations add AddAuditFields # ✅ 好
dotnet ef migrations add AddCreatedAtUpdatedAtCreatedByUpdatedByToAllTables # ❌ 太长
# 4. 按功能模块分组
dotnet ef migrations add Product_AddCategory
dotnet ef migrations add Order_AddShippingAddress
dotnet ef migrations add Customer_AddLoyaltyPoints❌ 避免的命名:
bash
# 1. 模糊的名称
dotnet ef migrations add Update1
dotnet ef migrations add Fix
dotnet ef migrations add Changes
# 2. 包含技术细节
dotnet ef migrations add AddColumnCategoryIdToProductsTable
dotnet ef migrations add CreateIndexOnProductName
# 3. 使用时间戳作为名称的一部分
dotnet ef migrations add 20240115_AddProduct # 时间戳已存在于文件名中2.3 迁移注释文档
在迁移类中添加 XML 注释:
csharp
/// <summary>
/// 为产品表添加分类支持
/// - 新增 Categories 表
/// - 为 Products 表添加 CategoryId 外键
/// - 迁移历史数据到默认分类
/// </summary>
[DbContext(typeof(AppDbContext))]
[Migration("20240115103000_AddProductCategory")]
public partial class AddProductCategory : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
// 步骤1: 创建分类表
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(100)", nullable: false),
Description = table.Column<string>(type: "nvarchar(500)", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_Categories", x => x.Id);
});
// 步骤2: 为产品表添加外键
migrationBuilder.AddColumn<int>(
name: "CategoryId",
table: "Products",
type: "int",
nullable: false,
defaultValue: 1); // 默认为"未分类"
migrationBuilder.AddForeignKey(
name: "FK_Products_Categories_CategoryId",
table: "Products",
column: "CategoryId",
principalTable: "Categories",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
// 步骤3: 插入默认分类
migrationBuilder.Sql(@"
INSERT INTO Categories (Id, Name, Description)
VALUES (1, '未分类', '默认分类')
");
}
protected override void Down(MigrationBuilder migrationBuilder)
{
// 反向操作...
}
}3. 迁移文件组织策略
3.1 默认目录结构
Project/
├── Migrations/
│ ├── 20240101120000_InitialCreate.cs
│ ├── 20240101120000_InitialCreate.Designer.cs
│ ├── 20240115103000_AddProductCategory.cs
│ ├── 20240115103000_AddProductCategory.Designer.cs
│ └── AppDbContextModelSnapshot.cs3.2 按功能模块组织
对于大型项目,可以按模块分文件夹:
Project/
├── Migrations/
│ ├── Core/ // 核心模块
│ │ ├── 20240101120000_InitialCreate.cs
│ │ └── 20240105140000_AddAuditFields.cs
│ ├── Products/ // 产品模块
│ │ ├── 20240110100000_AddCategories.cs
│ │ └── 20240115120000_AddProductImages.cs
│ ├── Orders/ // 订单模块
│ │ ├── 20240120090000_CreateOrders.cs
│ │ └── 20240125150000_AddOrderTracking.cs
│ └── AppDbContextModelSnapshot.cs配置自定义迁移目录:
csharp
// 在 DbContext 中指定
[Migration("20240115103000_AddProductCategory")]
public partial class AddProductCategory : Migration
{
// ...
}
// 命令行指定输出目录
dotnet ef migrations add AddProductCategory --output-dir Migrations/Products
// 或者在 .csproj 中配置
<Project>
<PropertyGroup>
<EfMigrationsDir>Migrations/Core</EfMigrationsDir>
</PropertyGroup>
</Project>3.3 多 DbContext 的迁移管理
当项目有多个 DbContext 时:
Project/
├── Migrations/
│ ├── AppDbContext/
│ │ ├── 20240101120000_InitialCreate.cs
│ │ └── AppDbContextModelSnapshot.cs
│ ├── IdentityDbContext/
│ │ ├── 20240101120000_InitialCreate.cs
│ │ └── IdentityDbContextModelSnapshot.cs
│ └── AuditDbContext/
│ ├── 20240101120000_InitialCreate.cs
│ └── AuditDbContextModelSnapshot.cs命令行指定 Context:
bash
# 为特定的 DbContext 创建迁移
dotnet ef migrations add AddUserRoles --context IdentityDbContext
dotnet ef migrations add AddAuditLogs --context AuditDbContext
# 应用特定 Context 的迁移
dotnet ef database update --context IdentityDbContext
dotnet ef database update --context AuditDbContext
# 生成脚本
dotnet ef migrations script --context AppDbContext4. 迁移冲突检测与解决
4.1 什么是迁移冲突?
冲突场景:
开发者 A 开发者 B
| |
|-- 基于快照 S1 |-- 基于快照 S1
| |
|-- 创建迁移 M_A |-- 创建迁移 M_B
| |
|-- 更新快照 → S2 |-- 更新快照 → S2' (过时!)
| |
|-- 提交代码 |-- 尝试提交代码 (冲突!)冲突表现:
bash
# 开发者 B 拉取代码后尝试创建新迁移
dotnet ef migrations add AddNewFeature
# 错误信息:
# The model snapshot is out of sync with the latest migration.
# Please update your model snapshot to match the latest migration.4.2 冲突预防策略
策略1: 频繁同步代码
bash
# 每次创建迁移前先拉取最新代码
git pull origin main
# 确保本地快照是最新的
dotnet ef dbcontext info
# 然后再创建迁移
dotnet ef migrations add AddNewFeature策略2: 迁移审查流程
markdown
## Pull Request 检查清单
- [ ] 确认迁移基于最新的模型快照
- [ ] 检查迁移文件名是否遵循命名规范
- [ ] 验证 Up 和 Down 方法是否正确
- [ ] 测试迁移是否可以成功应用
- [ ] 测试迁移是否可以回滚
- [ ] 确认迁移不会破坏现有数据策略3: CI/CD 自动化检测
yaml
# GitHub Actions 示例
name: Check EF Migrations
on: [pull_request]
jobs:
check-migrations:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Setup .NET
uses: actions/setup-dotnet@v3
with:
dotnet-version: '8.0.x'
- name: Install EF Tools
run: dotnet tool install --global dotnet-ef
- name: Check for pending migrations
run: |
dotnet ef migrations bundle
if [ $? -ne 0 ]; then
echo "Error: Model changes detected without corresponding migration"
exit 1
fi
- name: Verify migration can be applied
run: |
dotnet ef database update --connection "$TEST_DB_CONNECTION"
if [ $? -ne 0 ]; then
echo "Error: Migration cannot be applied"
exit 1
fi4.3 冲突解决方法
方法1: 重新生成迁移(推荐)
bash
# 步骤1: 删除冲突的迁移文件
rm Migrations/20240115103000_ConflictingMigration.cs
rm Migrations/20240115103000_ConflictingMigration.Designer.cs
# 步骤2: 拉取最新代码
git pull origin main
# 步骤3: 基于最新快照重新创建迁移
dotnet ef migrations add AddNewFeature
# 步骤4: 验证新生成的迁移
dotnet ef migrations script方法2: 合并迁移(复杂场景)
bash
# 场景: 两个开发者都修改了同一个实体
# 开发者 A 的迁移
public partial class AddProductWeight : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<decimal>(
name: "Weight",
table: "Products",
type: "decimal(18,2)",
nullable: false,
defaultValue: 0m);
}
}
// 开发者 B 的迁移(也修改了 Products 表)
public partial class AddProductDimensions : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<decimal>(
name: "Length",
table: "Products",
type: "decimal(18,2)",
nullable: false,
defaultValue: 0m);
migrationBuilder.AddColumn<decimal>(
name: "Width",
table: "Products",
type: "decimal(18,2)",
nullable: false,
defaultValue: 0m);
}
}
// ✅ 解决方案: 创建一个合并迁移
dotnet ef migrations add MergeProductSchemaChanges
public partial class MergeProductSchemaChanges : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
// 合并所有变更到一个迁移
migrationBuilder.AddColumn<decimal>(
name: "Weight",
table: "Products",
type: "decimal(18,2)",
nullable: false,
defaultValue: 0m);
migrationBuilder.AddColumn<decimal>(
name: "Length",
table: "Products",
type: "decimal(18,2)",
nullable: false,
defaultValue: 0m);
migrationBuilder.AddColumn<decimal>(
name: "Width",
table: "Products",
type: "decimal(18,2)",
nullable: false,
defaultValue: 0m);
}
}
// 删除旧的冲突迁移
rm Migrations/*_AddProductWeight.cs
rm Migrations/*_AddProductDimensions.cs方法3: 重置迁移历史(仅开发环境)
bash
# ⚠️ 警告: 仅在开发环境使用!
# 步骤1: 删除所有迁移文件
rm -rf Migrations/*.cs
# 步骤2: 删除数据库
dotnet ef database drop --force
# 步骤3: 重新创建初始迁移
dotnet ef migrations add InitialCreate
# 步骤4: 创建数据库
dotnet ef database update5. 迁移文件版本控制
5.1 Git 中的迁移文件
.gitignore 配置:
gitignore
# 不要忽略迁移文件!
# Migrations/*.cs 应该被追踪
# 但可以忽略临时文件
Migrations/*.tmp
Migrations/backup/提交迁移的最佳实践:
bash
# 1. 单独提交迁移文件(不与业务逻辑混合)
git add Migrations/20240115103000_AddProductCategory.cs
git add Migrations/20240115103000_AddProductCategory.Designer.cs
git commit -m "Add migration for product category support"
# 2. 然后提交相关代码变更
git add src/Models/Product.cs
git add src/Services/ProductService.cs
git commit -m "Implement product category feature"5.2 迁移文件标签化
使用 Git 标签标记重要迁移:
bash
# 为生产环境的迁移打标签
git tag -a v1.0.0-migration-20240101 -m "Initial production migration"
git push origin v1.0.0-migration-20240101
# 为重大架构变更打标签
git tag -a v2.0.0-migration-multitenancy -m "Multi-tenancy migration"
git push origin v2.0.0-migration-multitenancy
# 查看迁移标签
git tag -l "*migration*"5.3 迁移文档化
维护 MIGRATIONS.md 文件:
markdown
# EF Core 迁移记录
## 迁移列表
| 迁移ID | 名称 | 日期 | 作者 | 描述 | 状态 |
|--------|------|------|------|------|------|
| 20240101120000 | InitialCreate | 2024-01-01 | 张三 | 初始数据库架构 | ✅ 生产 |
| 20240115103000 | AddProductCategory | 2024-01-15 | 李四 | 产品分类功能 | ✅ 生产 |
| 20240201140000 | AddOrderTracking | 2024-02-01 | 王五 | 订单追踪功能 | 🚧 测试 |
| 20240210090000 | MultiTenancy | 2024-02-10 | 张三 | 多租户支持 | 📝 开发 |
## 重要变更记录
### 20240210090000_MultiTenancy
**影响范围**: 所有表
**停机时间**: 预计 30 分钟
**回滚策略**: 从备份还原
**变更详情**:
- 为所有表添加 TenantId 列
- 创建租户隔离索引
- 迁移历史数据到默认租户
**部署步骤**:
1. 备份数据库
2. 应用迁移: `dotnet ef database update`
3. 运行数据迁移脚本
4. 验证数据完整性
5. 更新应用配置6. 迁移文件清理与维护
6.1 何时清理迁移文件?
适合清理的场景:
✅ 开发阶段的原型迁移
✅ 测试用的临时迁移
✅ 合并冲突后的旧迁移
✅ 超过一定数量的累积迁移(可选)
不应该清理的场景:
❌ 已部署到生产环境的迁移
❌ 其他团队依赖的迁移
❌ 需要回滚支持的迁移
6.2 清理策略
策略1: 压缩迁移(Squash Migrations)
bash
# 场景: 有 50 个开发迁移,想简化为一个
# 步骤1: 删除所有迁移文件
rm -rf Migrations/*.cs
# 步骤2: 基于当前数据库创建新迁移
dotnet ef migrations add ConsolidatedMigration
# 步骤3: 更新模型快照
# (自动完成)
# ⚠️ 警告: 这会丢失迁移历史!策略2: 保留关键迁移,删除中间迁移
原始迁移历史:
├── 20240101_InitialCreate ← 保留(起点)
├── 20240102_AddField1 ← 删除
├── 20240103_AddField2 ← 删除
├── 20240104_ModifyField3 ← 删除
└── 20240105_AddMajorFeature ← 保留(里程碑)
压缩后:
├── 20240101_InitialCreate ← 保留
└── 20240105_AddMajorFeature ← 保留(包含所有变更)策略3: 归档旧迁移
bash
# 创建归档目录
mkdir -p Migrations/Archive/2024-Q1
# 移动旧迁移(已部署到生产的除外)
mv Migrations/202401*.cs Migrations/Archive/2024-Q1/
# 创建归档说明
cat > Migrations/Archive/2024-Q1/README.md << EOF
# 迁移归档 - 2024年第一季度
这些迁移已部署到生产环境,不应再次应用。
仅用于参考和历史记录。
## 迁移列表
- 20240101120000_InitialCreate.cs
- 20240115103000_AddProductCategory.cs
EOF6.3 重建迁移历史
完全重置(仅新项目):
bash
# 步骤1: 删除数据库
dotnet ef database drop --force
# 步骤2: 删除所有迁移文件
rm -rf Migrations/*.cs
# 步骤3: 创建干净的初始迁移
dotnet ef migrations add InitialCreate
# 步骤4: 创建数据库
dotnet ef database update
# 步骤5: 提交到版本控制
git add Migrations/
git commit -m "Reset migration history"7. 多环境迁移管理
7.1 环境配置
appsettings 配置:
json
// appsettings.Development.json
{
"ConnectionStrings": {
"DefaultConnection": "Server=localhost;Database=MyApp_Dev;"
},
"MigrationSettings": {
"AutoApplyMigrations": true,
"RequireConfirmation": false
}
}
// appsettings.Staging.json
{
"ConnectionStrings": {
"DefaultConnection": "Server=staging-server;Database=MyApp_Staging;"
},
"MigrationSettings": {
"AutoApplyMigrations": false,
"RequireConfirmation": true
}
}
// appsettings.Production.json
{
"ConnectionStrings": {
"DefaultConnection": "Server=prod-server;Database=MyApp_Production;"
},
"MigrationSettings": {
"AutoApplyMigrations": false,
"RequireConfirmation": true,
"GenerateScriptOnly": true
}
}7.2 环境特定的迁移服务
csharp
public class MigrationService
{
private readonly AppDbContext _context;
private readonly IConfiguration _configuration;
private readonly ILogger<MigrationService> _logger;
public MigrationService(
AppDbContext context,
IConfiguration configuration,
ILogger<MigrationService> logger)
{
_context = context;
_configuration = configuration;
_logger = logger;
}
public async Task ApplyMigrationsAsync()
{
var migrationSettings = _configuration.GetSection("MigrationSettings");
var autoApply = migrationSettings.GetValue<bool>("AutoApplyMigrations");
if (!autoApply)
{
_logger.LogInformation("Auto migration is disabled");
return;
}
try
{
// 获取待应用的迁移
var pendingMigrations = await _context.Database.GetPendingMigrationsAsync();
if (!pendingMigrations.Any())
{
_logger.LogInformation("No pending migrations");
return;
}
_logger.LogInformation("Applying {Count} pending migrations: {@Migrations}",
pendingMigrations.Count(),
pendingMigrations);
// 应用迁移
await _context.Database.MigrateAsync();
_logger.LogInformation("Migrations applied successfully");
}
catch (Exception ex)
{
_logger.LogError(ex, "Failed to apply migrations");
throw;
}
}
public async Task GenerateMigrationScriptAsync(string outputPath)
{
var pendingMigrations = await _context.Database.GetPendingMigrationsAsync();
if (!pendingMigrations.Any())
{
_logger.LogInformation("No pending migrations to script");
return;
}
// 生成 SQL 脚本
var script = await _context.Database.GenerateCreateScriptAsync();
await File.WriteAllTextAsync(outputPath, script);
_logger.LogInformation("Migration script generated: {Path}", outputPath);
}
}7.3 环境特定的迁移命令
bash
# 开发环境 - 自动应用
dotnet ef database update --environment Development
# 预生产环境 - 生成脚本审查
dotnet ef migrations script --idempotent --output migration_staging.sql
# 生产环境 - 仅生成脚本,手动执行
dotnet ef migrations script --idempotent --output migration_production.sql
# 使用环境变量指定连接字符串
DOTNET_ConnectionStrings__DefaultConnection="Server=prod;..." \
dotnet ef migrations script --idempotent8. 自动化迁移部署
8.1 CI/CD 流水线集成
GitHub Actions 完整示例:
yaml
name: Deploy Database Migrations
on:
push:
branches: [main]
paths:
- 'Migrations/**'
jobs:
validate-migrations:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Setup .NET
uses: actions/setup-dotnet@v3
with:
dotnet-version: '8.0.x'
- name: Install EF Tools
run: dotnet tool install --global dotnet-ef
- name: Build Project
run: dotnet build --configuration Release
- name: Validate Migrations
run: |
dotnet ef migrations bundle --force
./efbundle --connection "${{ secrets.TEST_DB_CONNECTION }}"
- name: Run Tests
run: dotnet test --no-build --verbosity normal
deploy-staging:
needs: validate-migrations
runs-on: ubuntu-latest
environment: staging
steps:
- uses: actions/checkout@v3
- name: Setup .NET
uses: actions/setup-dotnet@v3
- name: Install EF Tools
run: dotnet tool install --global dotnet-ef
- name: Generate Migration Script
run: |
dotnet ef migrations script --idempotent \
--output migration_staging.sql \
--connection "${{ secrets.STAGING_DB_CONNECTION }}"
- name: Review Migration Script
run: cat migration_staging.sql
- name: Apply Migrations to Staging
run: |
dotnet ef database update \
--connection "${{ secrets.STAGING_DB_CONNECTION }}"
- name: Run Integration Tests
run: dotnet test --filter "Category=Integration"
deploy-production:
needs: deploy-staging
runs-on: ubuntu-latest
environment: production
steps:
- uses: actions/checkout@v3
- name: Setup .NET
uses: actions/setup-dotnet@v3
- name: Install EF Tools
run: dotnet tool install --global dotnet-ef
- name: Generate Production Migration Script
run: |
dotnet ef migrations script --idempotent \
--output migration_production.sql \
--connection "${{ secrets.PROD_DB_CONNECTION }}"
- name: Upload Migration Script
uses: actions/upload-artifact@v3
with:
name: production-migration
path: migration_production.sql
- name: Create Backup Before Migration
run: |
# 调用备份 API 或脚本
curl -X POST "${{ secrets.BACKUP_API_URL }}" \
-H "Authorization: Bearer ${{ secrets.BACKUP_TOKEN }}"
- name: Wait for Approval
uses: trstringer/manual-approval@v1
with:
secret: ${{ github.TOKEN }}
approvers: admin1,admin2
minimum-approvals: 1
- name: Apply Migrations to Production
run: |
dotnet ef database update \
--connection "${{ secrets.PROD_DB_CONNECTION }}"
- name: Verify Deployment
run: |
# 运行健康检查
curl -f "${{ secrets.APP_URL }}/health" || exit 1
# 检查迁移版本
dotnet ef migrations list --connection "${{ secrets.PROD_DB_CONNECTION }}"8.2 Azure DevOps 管道
yaml
# azure-pipelines.yml
trigger:
branches:
include:
- main
paths:
include:
- Migrations/*
stages:
- stage: Validate
jobs:
- job: TestMigrations
pool:
vmImage: 'ubuntu-latest'
steps:
- task: DotNetCoreCLI@2
displayName: 'Install EF Tools'
inputs:
command: 'custom'
custom: 'tool'
arguments: 'install --global dotnet-ef'
- task: DotNetCoreCLI@2
displayName: 'Validate Migrations'
inputs:
command: 'custom'
custom: 'ef'
arguments: 'database update --connection $(TestDbConnection)'
- stage: DeployStaging
dependsOn: Validate
jobs:
- deployment: DeployToStaging
environment: staging
strategy:
runOnce:
deploy:
steps:
- script: |
dotnet ef database update --connection $(StagingDbConnection)
displayName: 'Apply Migrations'
- stage: DeployProduction
dependsOn: DeployStaging
jobs:
- deployment: DeployToProduction
environment: production
strategy:
runOnce:
deploy:
steps:
- script: |
dotnet ef migrations script --idempotent --output migration.sql
displayName: 'Generate Script'
- script: |
sqlcmd -S $(ProdServer) -d $(ProdDatabase) -i migration.sql
displayName: 'Execute Migration'8.3 Docker 容器中的迁移
Dockerfile 集成:
dockerfile
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
WORKDIR /src
COPY . .
RUN dotnet restore
RUN dotnet build -c Release -o /app/build
FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS runtime
WORKDIR /app
COPY --from=build /app/build .
# 安装 EF Tools
RUN dotnet tool install --global dotnet-ef
ENV PATH="${PATH}:/root/.dotnet/tools"
# 复制迁移文件
COPY Migrations ./Migrations
# 入口点脚本
COPY entrypoint.sh .
RUN chmod +x entrypoint.sh
ENTRYPOINT ["./entrypoint.sh"]entrypoint.sh:
bash
#!/bin/bash
set -e
echo "Waiting for database to be ready..."
until dotnet ef database info --connection "$CONNECTION_STRING" > /dev/null 2>&1; do
sleep 2
done
echo "Applying migrations..."
dotnet ef database update --connection "$CONNECTION_STRING"
if [ $? -eq 0 ]; then
echo "Migrations applied successfully"
else
echo "Failed to apply migrations"
exit 1
fi
echo "Starting application..."
exec dotnet MyApp.dll总结
迁移文件管理是 EF Core 项目长期健康的关键:
核心原则
✅ 命名规范 - 使用清晰的动词+名词模式
✅ 版本控制 - 所有迁移文件都应纳入 Git 管理
✅ 冲突预防 - 频繁同步,及时审查
✅ 环境隔离 - 不同环境采用不同策略
✅ 自动化部署 - CI/CD 集成,减少人为错误
最佳实践
- 开发环境 - 允许自动迁移,快速迭代
- 测试环境 - 生成脚本审查后应用
- 生产环境 - 仅生成脚本,DBA 手动执行
- 迁移压缩 - 定期清理开发迁移,保留里程碑
- 文档化 - 维护 MIGRATIONS.md 记录所有变更
工具链
- dotnet ef CLI - 核心工具
- CI/CD 集成 - GitHub Actions, Azure DevOps
- Docker 支持 - 容器化部署
- 监控告警 - 迁移失败自动通知
掌握这些迁移文件管理技巧,你可以高效地协作开发并保证数据库架构的一致性!