☕ NEW! 完成新手任務即可參加抽獎!LINE 星巴克禮券等你拿,名額有限!        🎉 推廣活動:邀請好友註冊 DevLearn,累積推薦抽 LINE 星巴克禮券! 活動詳情 →        🔥 活動期間 2026/4/1 - 5/31 |已有 0 人參加       
資料庫 中級

資料庫遷移 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 腳本

💡 大家的想法 · 0

載入中...
💬 即時聊天室 🟢 0 人在線
😀 😎 🤓 💻 🎮 🎸 🔥
➕ 新問題
📋 我的工單
💬 LINE 社群
🔒
需要註冊才能使用此功能
註冊帳號即可解鎖測驗、遊戲、簽到、筆記下載等所有功能,完全免費!
免費註冊