it

C# Entity Framework Zero Downtime Deployment

C# Entity Framework Zero Downtime Deployment

C# Entity Framework Zero Downtime Deployment คืออะไร

C# Entity Framework Zero Downtime Deployment

Entity Framework (EF) Core เป็น ORM (Object-Relational Mapper) หลักของ .NET ที่ช่วย developers ทำงานกับ database ผ่าน C# objects แทนการเขียน SQL โดยตรง Zero Downtime Deployment คือการ deploy application version ใหม่โดยไม่มี downtime — ผู้ใช้ไม่รู้สึกว่าระบบหยุดทำงาน ความท้าทายหลักคือ database migrations ที่ต้อง backward compatible เพราะระหว่าง deploy มีทั้ง version เก่าและใหม่ทำงานพร้อมกัน บทความนี้อธิบายวิธี deploy EF Core applications แบบ zero downtime พร้อมตัวอย่าง code

Zero Downtime Deployment Strategies

# zdt_strategies.py — Zero downtime deployment strategies

import json



class ZDTStrategies:

    STRATEGIES = {

        "rolling": {

            "name": "Rolling Deployment",

            "description": "Deploy ทีละ instance — old + new versions run พร้อมกัน",

            "requirement": "Database schema ต้อง compatible กับทั้ง 2 versions",

            "tools": "Kubernetes rolling update, Azure App Service slots",

        },

        "blue_green": {

            "name": "Blue-Green Deployment",

            "description": "Deploy version ใหม่ (Green) แยก → switch traffic → retire Blue",

            "requirement": "Database shared ระหว่าง Blue + Green → migration ต้อง backward compatible",

            "tools": "Kubernetes services, AWS ALB, Azure Traffic Manager",

        },

        "canary": {

            "name": "Canary Deployment",

            "description": "ส่ง traffic 5-10% ไป version ใหม่ → monitor → ค่อยเพิ่ม",

            "requirement": "เหมือน Rolling — schema ต้อง compatible",

            "tools": "Istio, Flagger, AWS App Mesh",

        },

    }



    CHALLENGES = {

        "schema_change": "Schema changes (add/remove columns) ต้อง backward compatible",

        "data_migration": "Data migration ต้องไม่ lock tables นาน",

        "rollback": "ต้อง rollback ได้ถ้า version ใหม่มีปัญหา",

        "connection_pool": "Connection pool ต้องไม่ขาด ระหว่าง deploy",

        "ef_migration": "EF Migrations ต้อง idempotent + backward compatible",

    }



    def show_strategies(self):

        print("=== ZDT Strategies ===\n")

        for key, strat in self.STRATEGIES.items():

            print(f"[{strat['name']}]")

            print(f"  {strat['description']}")

            print(f"  Requirement: {strat['requirement']}")

            print()



    def show_challenges(self):

        print("=== Challenges ===")

        for key, challenge in self.CHALLENGES.items():

            print(f"  [{key}] {challenge}")



zdt = ZDTStrategies()

zdt.show_strategies()

zdt.show_challenges()

EF Core Migration Best Practices

# 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

C# Entity Framework Zero Downtime Deployment
# 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()

Python Migration Validator

# validator.py — Validate EF migrations for ZDT safety

import json



class MigrationValidator:

    CODE = """

# ef_migration_validator.py — Check if migrations are ZDT-safe

import re

import json

from pathlib import Path



class MigrationSafetyChecker:

    UNSAFE_PATTERNS = {

        'DropColumn': {

            'pattern': r'migrationBuilder\\.DropColumn',

            'severity': 'critical',

            'message': 'DROP COLUMN breaks old app versions reading this column',

            'fix': 'Use expand-contract: add new column → migrate → drop old',

        },

        'RenameColumn': {

            'pattern': r'migrationBuilder\\.RenameColumn',

            'severity': 'critical',

            'message': 'RENAME COLUMN breaks old app versions',

            'fix': 'Use expand-contract: add new → copy data → drop old',

        },

        'DropTable': {

            'pattern': r'migrationBuilder\\.DropTable',

            'severity': 'high',

            'message': 'DROP TABLE may break old app versions',

            'fix': 'Only drop after all app versions stop using this table',

        },

        'AlterColumn_NotNull': {

            'pattern': r'nullable:\\s*false',

            'severity': 'high',

            'message': 'Adding NOT NULL constraint may fail for existing rows',

            'fix': 'Add with default value or make nullable first',

        },

        'RenameTable': {

            'pattern': r'migrationBuilder\\.RenameTable',

            'severity': 'critical',

            'message': 'RENAME TABLE breaks old app versions',

            'fix': 'Create new table → migrate data → drop old',

        },

    }

    

    def check_file(self, filepath):

        '''Check a single migration file for unsafe patterns'''

        content = Path(filepath).read_text()

        issues = []

        

        for name, rule in self.UNSAFE_PATTERNS.items():

            matches = re.findall(rule['pattern'], content)

            if matches:

                issues.append({

                    'rule': name,

                    'severity': rule['severity'],

                    'message': rule['message'],

                    'fix': rule['fix'],

                    'occurrences': len(matches),

                })

        

        return {

            'file': str(filepath),

            'safe': len(issues) == 0,

            'issues': issues,

        }

    

    def check_directory(self, migrations_dir):

        '''Check all migration files in directory'''

        results = []

        for f in sorted(Path(migrations_dir).glob('*.cs')):

            if 'Designer' not in f.name:

                results.append(self.check_file(f))

        

        total = len(results)

        safe = sum(1 for r in results if r['safe'])

        unsafe = total - safe

        

        return {

            'total_migrations': total,

            'safe': safe,

            'unsafe': unsafe,

            'details': [r for r in results if not r['safe']],

        }



# checker = MigrationSafetyChecker()

# result = checker.check_directory("./Migrations")

# print(json.dumps(result, indent=2))

"""



    def show_code(self):

        print("=== Migration Validator ===")

        print(self.CODE[:600])



validator = MigrationValidator()

validator.show_code()

CI/CD Pipeline

# cicd.py — CI/CD pipeline for ZDT with EF Core

import json



class ZDTPipeline:

    GITHUB_ACTIONS = """

# .github/workflows/deploy-zdt.yml

name: Zero Downtime Deploy

on:

  push:

    branches: [main]



jobs:

  validate-migrations:

    runs-on: ubuntu-latest

    steps:

      - uses: actions/checkout@v4

      

      - name: Setup .NET

        uses: actions/setup-dotnet@v4

        with:

          dotnet-version: '8.0'

      

      - name: Check Migration Safety

        run: |

          python scripts/check_migrations.py ./src/Migrations

          if [ $? -ne 0 ]; then

            echo "UNSAFE migrations detected!"

            exit 1

          fi

      

      - name: Run Tests

        run: dotnet test --configuration Release



  deploy:

    needs: validate-migrations

    runs-on: ubuntu-latest

    steps:

      - name: Apply Migrations

        run: |

          dotnet ef database update \\

            --connection "}" \\

            --project src/MyApp

      

      - name: Rolling Deploy

        run: |

          kubectl set image deployment/myapp \\

            myapp=myapp:} \\

            --record

          kubectl rollout status deployment/myapp \\

            --timeout=300s

      

      - name: Smoke Test

        run: |

          curl -f https://myapp.example.com/health || exit 1

      

      - name: Rollback on Failure

        if: failure()

        run: |

          kubectl rollout undo deployment/myapp

"""



    DEPLOY_ORDER = [

        "1. Validate: Check migration safety (no unsafe patterns)",

        "2. Test: Run unit + integration tests with new schema",

        "3. Migrate: Apply DB migrations (backward compatible)",

        "4. Deploy: Rolling update — old + new run together",

        "5. Verify: Health checks + smoke tests",

        "6. Monitor: Watch error rates, latency for 15 min",

        "7. Cleanup: Next deploy — remove old columns/tables if needed",

    ]



    def show_pipeline(self):

        print("=== CI/CD Pipeline ===")

        print(self.GITHUB_ACTIONS[:500])



    def show_order(self):

        print(f"\n=== Deployment Order ===")

        for step in self.DEPLOY_ORDER:

            print(f"  {step}")



pipeline = ZDTPipeline()

pipeline.show_pipeline()

pipeline.show_order()

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 ใหม่ได้

เนื้อหาเกี่ยวข้อง — อ่านต่อ: Azure Functions Message Queue Design

Q: ถ้า migration ผิดพลาด rollback ยังไง?

แนะนำเพิ่มเติม — XM Signal

A: EF Core: dotnet ef database update [PreviousMigrationName] — rollback ไป migration ก่อนหน้า แต่: ถ้า migration ทำ data transformation — rollback อาจสูญเสียข้อมูล Best practice: backup database ก่อน migrate เสมอ ป้องกัน: test migration บน staging ก่อน production + ใช้ expand-contract pattern

เนื้อหาเกี่ยวข้อง — ดูเพิ่มเติมเรื่อง PostgreSQL JSONB Monitoring และ Alerting — คู่มือฉบับสมบูรณ์ 2026

Q: Expand-Contract ต้องใช้กี่ deploys?

A: ขั้นต่ำ 3 deploys: Deploy 1 (Expand): เพิ่ม column ใหม่ + sync data Deploy 2 (Migrate): app ใช้ column ใหม่เท่านั้น Deploy 3 (Contract): ลบ column เก่า ข้อเสีย: ช้ากว่า deploy เดียว แต่ไม่มี downtime เลย เหมาะกับ: production systems ที่ downtime ยอมรับไม่ได้

แนะนำเพิ่มเติม — เรียนเทรดกับ iCafeForex

เนื้อหาเกี่ยวข้อง — แนะนำให้อ่าน OPA Gatekeeper Database Migration

Q: EF Core กับ Dapper อันไหนดีสำหรับ ZDT?

A: EF Core: มี migration system built-in, schema ผูกกับ model — เปลี่ยน model = ต้อง migrate Dapper: ไม่มี migration (ใช้ FluentMigrator/DbUp แทน), SQL เขียนเอง — flexible กว่า สำหรับ ZDT: ทั้งสองทำได้ — สำคัญคือ migration strategy ไม่ใช่ ORM EF Core ง่ายกว่า: migration tooling ดี, model-first approach Dapper ยืดหยุ่นกว่า: ควบคุม SQL ได้เต็มที่

เนื้อหาเกี่ยวข้อง — แนะนำให้อ่าน จอมอนิเตอร์ 2k — ทุกสิ่งที่ต้องรู้ในปี 2026

XM Legend · เทรดเดอร์ & ผู้สอน Forex 13 ปี

ผู้ก่อตั้ง SiamCafe ตั้งแต่ปี 1997 · เทรดเดอร์สาย Forex มากกว่า 13 ปี ได้รับการยกย่องเป็น XM Legend · แบ่งปันความรู้ Forex, ไอที, AI และการเทรด จากประสบการณ์จริงในตลาดจริง