# ef_migrations.py — EF Core migration patterns for ZDT
import json
class EFMigrationPatterns:
SAFE_OPERATIONS = """
// === SAFE Operations (backward compatible) ===
// 1. ADD column (nullable or with default)
migrationBuilder.AddColumn(
name: "MiddleName",
table: "Users",
type: "nvarchar(100)",
nullable: true); // nullable = safe
// 2. ADD table
migrationBuilder.CreateTable(
name: "UserPreferences",
columns: table => new {
Id = table.Column(nullable: false)
.Annotation("SqlServer:Identity", "1, 1"),
UserId = table.Column(nullable: false),
Theme = table.Column(maxLength: 50, nullable: true),
});
// 3. ADD index (CONCURRENTLY on PostgreSQL)
migrationBuilder.CreateIndex(
name: "IX_Users_Email",
table: "Users",
column: "Email");
// 4. RENAME via expand-contract pattern
// Step 1 (deploy v1.1): Add new column
migrationBuilder.AddColumn(
name: "FullName", table: "Users", nullable: true);
// Step 2 (deploy v1.2): Copy data + use new column
// Step 3 (deploy v1.3): Drop old column
"""
UNSAFE_OPERATIONS = """
// === UNSAFE Operations (cause downtime) ===
// ❌ DROP column — old version still reads it
migrationBuilder.DropColumn(name: "OldField", table: "Users");
// ❌ RENAME column — old version can't find it
migrationBuilder.RenameColumn(
name: "Name", table: "Users", newName: "FullName");
// ❌ Change column type — may lose data
migrationBuilder.AlterColumn(
name: "Age", table: "Users", type: "int");
// ❌ Add NOT NULL column without default
migrationBuilder.AddColumn(
name: "RequiredField", table: "Users", nullable: false);
// Old version inserts without this field → error!
"""
def show_safe(self):
print("=== Safe Operations ===")
print(self.SAFE_OPERATIONS[:600])
def show_unsafe(self):
print("\n=== Unsafe Operations ===")
print(self.UNSAFE_OPERATIONS[:500])
patterns = EFMigrationPatterns()
patterns.show_safe()
patterns.show_unsafe()
Expand-Contract Pattern
# expand_contract.py — Expand-Contract migration pattern
import json
class ExpandContractPattern:
PATTERN = """
// === Expand-Contract Pattern for Column Rename ===
// Goal: Rename "Name" → "FullName" without downtime
// === Phase 1: EXPAND (Deploy v2.0) ===
// Add new column, keep old column
public partial class AddFullNameColumn : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
// Add new column (nullable)
migrationBuilder.AddColumn(
name: "FullName",
table: "Users",
type: "nvarchar(200)",
nullable: true);
// Copy existing data
migrationBuilder.Sql(
"UPDATE Users SET FullName = Name WHERE FullName IS NULL");
// Add trigger to sync (optional)
migrationBuilder.Sql(@"
CREATE TRIGGER trg_SyncFullName ON Users
AFTER INSERT, UPDATE AS
BEGIN
UPDATE u SET u.FullName = i.Name
FROM Users u INNER JOIN inserted i ON u.Id = i.Id
WHERE u.FullName IS NULL OR u.FullName != i.Name
END");
}
}
// App v2.0: Read from FullName, write to BOTH Name + FullName
// Old app v1.x: Still reads/writes Name — works fine
// === Phase 2: MIGRATE (Deploy v2.1) ===
// App reads/writes only FullName
// Verify all data migrated
// === Phase 3: CONTRACT (Deploy v2.2) ===
public partial class DropNameColumn : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
// Drop trigger
migrationBuilder.Sql("DROP TRIGGER IF EXISTS trg_SyncFullName");
// Drop old column (safe — no app reads it anymore)
migrationBuilder.DropColumn(name: "Name", table: "Users");
}
}
"""
TIMELINE = [
"Deploy v2.0: Add FullName column + sync trigger (old app works)",
"Deploy v2.1: App uses FullName only (old column still exists)",
"Verify: All data migrated, no reads on old column",
"Deploy v2.2: Drop old Name column + trigger (cleanup)",
]
def show_pattern(self):
print("=== Expand-Contract Pattern ===")
print(self.PATTERN[:600])
def show_timeline(self):
print(f"\n=== Deployment Timeline ===")
for step in self.TIMELINE:
print(f" → {step}")
ec = ExpandContractPattern()
ec.show_pattern()
ec.show_timeline()
FAQ - คำถามที่พบบ่อย
Q: EF Migration ต้อง run ก่อน deploy app ใหม่หรือเปล่า?
A: ใช่ — ต้อง migrate database ก่อน deploy app version ใหม่ เพราะ: app ใหม่อาจต้องการ columns/tables ใหม่ที่ migration สร้าง ลำดับ: 1) Apply migration → 2) Deploy new app → 3) Old app ยัง run ได้ (backward compatible) สำคัญ: migration ต้อง backward compatible — old app version ต้อง work กับ schema ใหม่ได้