資料庫遷移 Migration
為什麼需要 Migration?
想像你在蓋房子,每次要改格局(加房間、拆牆)都需要一份施工記錄。Migration 就是資料庫的施工記錄——記錄每一次結構變更,讓你可以追蹤、重現、甚至回退。
Code First vs Database First
| 方式 | 說明 | 適用場景 |
|---|---|---|
| Code First | 先寫 C# Entity → 自動產生資料庫 | 新專案、敏捷開發 |
| Database First | 先有資料庫 → 用工具產生 C# Entity | 既有資料庫、DBA 設計 |
// Code First:先定義 Entity
public class Student // 定義學生 Entity
{
public int Id { get; set; } // 主鍵
public string Name { get; set; } = ""; // 姓名
public string Email { get; set; } = ""; // 信箱
public int Score { get; set; } // 分數
}
// EF Core 會根據這個類別自動建立資料表
Migration 基本指令
建立 Migration
# 建立新的 Migration(PMC,Package Manager Console)
Add-Migration InitialCreate # 建立名為 InitialCreate 的遷移
# 或用 .NET CLI
dotnet ef migrations add InitialCreate # 同上,使用命令列工具
套用 Migration
# 更新資料庫(套用所有未套用的 Migration)
Update-Database # PMC 指令
# 或用 .NET CLI
dotnet ef database update # 同上,使用命令列工具
查看 Migration 狀態
# 列出所有 Migration 及其套用狀態
dotnet ef migrations list # 顯示所有遷移及是否已套用
Migration 檔案結構
每次 Add-Migration 會產生三個檔案:
Migrations/
├── 20240101120000_InitialCreate.cs # 遷移內容(Up/Down 方法)
├── 20240101120000_InitialCreate.Designer.cs # 快照設計器(自動產生)
└── AppDbContextModelSnapshot.cs # 模型快照(目前資料庫狀態)
// 20240101120000_InitialCreate.cs — 遷移檔案範例
public partial class InitialCreate : Migration // 繼承 Migration 類別
{
protected override void Up(MigrationBuilder migrationBuilder) // 升級方法
{
migrationBuilder.CreateTable( // 建立資料表
name: "Students", // 表名
columns: table => new // 定義欄位
{
Id = table.Column<int>(nullable: false) // 主鍵欄位
.Annotation("SqlServer:Identity", "1, 1"), // 自動遞增
Name = table.Column<string>(maxLength: 100), // 姓名欄位
Email = table.Column<string>(maxLength: 200), // 信箱欄位
Score = table.Column<int>(nullable: false, defaultValue: 0) // 分數欄位
},
constraints: table => // 設定約束
{
table.PrimaryKey("PK_Students", x => x.Id); // 主鍵約束
});
migrationBuilder.CreateIndex( // 建立索引
name: "IX_Students_Email", // 索引名稱
table: "Students", // 資料表
column: "Email", // 欄位
unique: true); // 唯一索引
}
protected override void Down(MigrationBuilder migrationBuilder) // 降級方法(回退用)
{
migrationBuilder.DropTable("Students"); // 刪除資料表
}
}
在 Migration 中植入種子資料
// 方法一:在 OnModelCreating 中用 HasData
protected override void OnModelCreating(ModelBuilder modelBuilder) // 模型建構
{
modelBuilder.Entity<Student>().HasData( // 植入種子資料
new Student { Id = 1, Name = "小賢", Email = "xian@test.com", Score = 95 }, // 學生 1
new Student { Id = 2, Name = "小明", Email = "ming@test.com", Score = 88 } // 學生 2
);
}
// 方法二:在 Migration 的 Up 方法中直接寫 SQL
protected override void Up(MigrationBuilder migrationBuilder) // 升級方法
{
migrationBuilder.Sql( // 執行原始 SQL
@"INSERT INTO Students (Name, Email, Score) -- 插入種子資料
VALUES (N'小賢', 'xian@test.com', 95), -- 學生 1
(N'小明', 'ming@test.com', 88)" -- 學生 2
);
}
回退 Migration
# 回退到指定的 Migration
dotnet ef database update InitialCreate # 回退到 InitialCreate(會執行 Down 方法)
# 回退到最初狀態(移除所有 Migration)
dotnet ef database update 0 # 回退到零(執行所有 Down 方法)
# 移除最後一個未套用的 Migration 檔案
dotnet ef migrations remove # 刪除最後一個 Migration 檔案
實用技巧
產生 SQL 腳本(不直接執行)
# 產生 SQL 腳本,用於正式環境部署
dotnet ef migrations script # 產生所有 Migration 的 SQL
# 指定範圍
dotnet ef migrations script FromMigration ToMigration # 產生指定範圍的 SQL
# 產生冪等腳本(可重複執行)
dotnet ef migrations script --idempotent # 加上 IF NOT EXISTS 檢查
手動修改 Migration
// 有時候自動產生的 Migration 不夠用,可以手動修改
protected override void Up(MigrationBuilder migrationBuilder) // 升級方法
{
// 重新命名欄位(EF Core 預設會刪除再建立,資料會遺失!)
migrationBuilder.RenameColumn( // 手動改成重新命名
name: "StudentName", // 原始欄位名
table: "Students", // 資料表
newName: "Name"); // 新欄位名
}
🤔 我這樣寫為什麼會錯?
❌ 錯誤 1:修改已發佈的 Migration
# ❌ 已經 Update-Database 之後,又修改 Migration 檔案的內容
# 這會導致 Migration 的雜湊值不一致,EF Core 會報錯
# "The migration '20240101_Init' has already been applied to the database"
# ✅ 正確做法:建立一個新的 Migration 來修改
dotnet ef migrations add FixStudentTable # 建立新的修正遷移
dotnet ef database update # 套用新的遷移
❌ 錯誤 2:沒有檢查自動產生的 SQL
// ❌ 直接 Update-Database,沒有看過產生的 SQL
// 可能會不小心刪除欄位(EF Core 會把重新命名誤判為刪除+新增)
# ✅ 先產生 SQL 腳本檢查
dotnet ef migrations script --idempotent # 先看 SQL 再決定是否執行
# 確認 SQL 沒問題後再 Update-Database
❌ 錯誤 3:在正式環境直接用 Update-Database
# ❌ 在正式環境直接執行 Update-Database
# 如果 Migration 有問題,可能會導致資料遺失!
# ✅ 正式環境應該用 SQL 腳本部署
dotnet ef migrations script --idempotent -o migrate.sql # 產生腳本
# 由 DBA 審核後,在正式環境執行 SQL 腳本
💡 重點整理
| 概念 | 說明 |
|---|---|
| Code First | 先寫 C# Entity,再產生資料庫 |
| Add-Migration | 建立新的遷移檔案 |
| Update-Database | 套用遷移到資料庫 |
| Up / Down | 升級和回退的方法 |
| HasData | 在模型中定義種子資料 |
| --idempotent | 產生可重複執行的 SQL 腳本 |