Skip to main content

Command Palette

Search for a command to run...

EF Core - Deployment: Migration Bundles

Published
5 min read

1. Overview

A Migration Bundle is a single-file executable introduced in Entity Framework Core 6. It encapsulates all database migrations and the logic required to apply them, allowing for database schema updates without requiring the .NET SDK or source code on the target environment.

Key Benefits

  • Zero Dependencies: Runs on target servers (Linux/Windows) without the .NET SDK.

  • Idempotency: Automatically checks the database state and applies only missing migrations.

  • CI/CD Compatibility: Designed to be executed as a standalone step in deployment pipelines (Azure DevOps, GitHub Actions, Docker).

  • Safety: Eliminates the concurrency risks associated with running Database.Migrate() during application startup.

Comparison of Deployment Methods

Featuredotnet ef database updateDatabase.Migrate() (In-Code)Migration Bundle
Requires .NET SDK?YesNoNo
Production Ready?No (Dev only)No (Concurrency risks)Yes
Execution ContextDeveloper MachineApplication StartupCI/CD Pipeline / Release Step
Artifact TypeCLI CommandDLL Method CallExecutable Binary

2. The Development & Build Lifecycle

Phase 1: Local Development

Database changes originate from developers modifying the Entity models.

  1. Change Implementation: Developers modify C# entity classes (e.g., adding properties).

  2. Migration Generation: The command dotnet ef migrations add <Name> generates:

    • A timestamped migration file (e.g., 20231027_AddCol.cs) containing Up() and Down() methods.

    • An updated ModelSnapshot.cs representing the current database state.

  3. Conflict Resolution:

    • If multiple developers modify the database schema simultaneously, git merge conflicts will occur in ModelSnapshot.cs.

    • Resolution: The developer merging last must revert their local migration, pull the latest changes, and regenerate their migration to ensure it builds on top of the correct snapshot.

Phase 2: Build Process (CI)

The Continuous Integration system compiles the code into artifacts. Two distinct artifacts are generated:

  1. Application Artifact: The compiled web application/API (DLLs).

  2. Database Artifact: The Migration Bundle (Executable).

Command to Generate Bundle:

dotnet ef migrations bundle --self-contained -r linux-x64 --output efbundle
  • --self-contained: Bundles the .NET runtime (increases size, removes dependencies).

  • -r <rid>: Specifies the target OS (e.g., linux-x64, win-x64).

  • --context: Specifies the DbContext if multiple exist.


3. Deployment Process (CD)

Execution Flow

Deployments follow a "Database First, App Second" approach to ensure the schema supports the application before traffic begins.

  1. Deploy Bundle: The efbundle executable is copied to the target environment (Staging/Production).

  2. Execute Bundle: The bundle is executed with the target connection string.

     ./efbundle --connection "Server=...;Database=...;"
    
    • Alternative: The bundle can read connection strings from environment variables (e.g., ConnectionStrings__DefaultConnection) automatically.
  3. History Check: The bundle queries the __EFMigrationsHistory table.

  4. Apply Deltas: It identifies and executes only the migrations that have not yet been recorded in the history table.

  5. Deploy Application: Once the bundle exits successfully (Exit Code 0), the new application version is deployed.

Multi-Developer Commits

When multiple developers contribute different migration files, the bundle aggregates them.

  • Result: Upon execution, the __EFMigrationsHistory table will record individual entries for every applied migration file, preserving the complete audit trail of changes committed by all developers.

4. Review Process & Quality Control

Since the bundle is a binary file, it cannot be reviewed directly. The review process focuses on the source and generated SQL.

Artifacts for Review

  1. Source Code (C#):

    • Focus: Check Up() methods for destructive operations (DropColumn, DropTable).

    • SQL Injection: Scrutinize any usage of migrationBuilder.Sql("...").

  2. Generated SQL Script (Recommended):

    • The CI pipeline should generate a SQL script artifact for review by DBAs.

    • Command: dotnet ef migrations script --output preview.sql --idempotent

    • This provides a human-readable verification of exactly what SQL commands will run against production.


5. Rollback & Recovery Strategies

Strategy A: The "Down" Migration (Targeted Rollback)

This strategy reverts the database schema to a previous state using the Down() methods defined in the migration files.

  • Requirement: Developers must implement robust Down() logic in every migration.

  • Execution: Run the bundle with the --target flag specifying the last known good migration.

      ./efbundle --target 20231029_PreviousGoodState
    
  • Outcome: The bundle executes the Down() logic of all migrations newer than the target and removes them from the history table.

  • Post-Action: The application must be immediately rolled back to the matching binary version.

Strategy B: Roll Forward (Hotfix)

Preferred for production environments to avoid data loss risks associated with Down() scripts.

  • Process:

    1. Do not revert the database.

    2. Create a new migration that fixes the issue or manually reverts changes (e.g., RenameColumn back to original).

    3. Build and deploy a new bundle.

  • Benefit: Maintains a linear history and avoids destructive reverse operations.

Strategy C: Expand and Contract (Zero Downtime)

Designed to decouple deployment from release.

  1. Expand: Add new columns/tables but keep old ones. Deploy. The DB supports both old and new app versions.

  2. Migrate: Update the application to use new columns.

  3. Contract: After stability is confirmed, deploy a cleanup migration to remove old columns.


6. Best Practices

  1. Separation of Concerns: Never run migrations inside Program.cs or application startup code in production. Run them as a distinct "Release" or "Init" step.

  2. Environment Variables: Do not embed connection strings in the bundle build. Inject them at runtime via environment variables to maintain security.

  3. Idempotency Checks: Always verify generated SQL scripts (dotnet ef migrations script) in critical environments to ensure no accidental data loss occurs.

  4. Non-Destructive Migrations: Prefer adding nullable columns over renaming or dropping columns to ensure backward compatibility during deployment.

  5. Build Once, Deploy Many: The same efbundle executable generated in the Build phase should be promoted through environments (Dev -> Staging -> Prod) to ensure consistency.