⚠ This page is served via a proxy. Original site: https://github.com
This service does not collect credentials or authentication data.
Skip to content

Conversation

@thromel
Copy link
Contributor

@thromel thromel commented Dec 23, 2025

Summary

Implements #37342: Allow creating and applying migrations at runtime without recompiling.

This adds support for creating and applying migrations at runtime using Roslyn compilation, enabling scenarios like .NET Aspire and containerized applications where recompilation isn't possible.

CLI Usage

# Standard update (existing behavior)
dotnet ef database update [migration]

# Create and apply a new migration in one step
dotnet ef database update MigrationName --add [--output-dir <DIR>] [--namespace <NS>] [--json]

The -o/--output-dir, -n/--namespace, and --json options require --add to be specified.

PowerShell Usage

# Standard update (existing behavior)
Update-Database [-Migration <migration>]

# Create and apply a new migration in one step
Update-Database -Migration MigrationName -Add [-OutputDir <DIR>] [-Namespace <NS>]

Architecture

Component Purpose
IMigrationCompiler / CSharpMigrationCompiler Internal: Roslyn-based compilation of scaffolded migrations
IMigrationsAssembly.AddMigrations(Assembly) Registers dynamically compiled migrations
MigrationsOperations.AddAndApplyMigration() Orchestrates scaffold → compile → register → apply workflow

Design Decisions

  • Extends existing services: Uses IMigrationsScaffolder for scaffolding and IMigrator for applying, adding only the new IMigrationCompiler service
  • AddMigrations(Assembly): Extended IMigrationsAssembly interface to accept additional assemblies containing runtime-compiled migrations
  • Always persists to disk: Like AddMigration, files are always saved to enable source control and future recompilation
  • No pending changes behavior: If no model changes are detected, applies any existing pending migrations without creating a new one
  • Internal compiler API: IMigrationCompiler and CSharpMigrationCompiler are in the .Internal namespace as they require design work for public API
  • Error handling with cleanup: If compilation or migration application fails, saved migration files are cleaned up to prevent orphans
  • Thread safety: MigrationsAssembly uses locking to protect against race conditions when adding migrations concurrently

Workflow

User runs: dotnet ef database update InitialCreate --add
    │
    ▼
MigrationsOperations.AddAndApplyMigration()
    │
    ├─► Check for pending model changes
    │       └─► If none: apply existing migrations, return
    │
    ├─► IMigrationsScaffolder.ScaffoldMigration() - Generate code
    │
    ├─► try {
    │       ├─► IMigrationsScaffolder.Save() - Write files to disk
    │       ├─► IMigrationCompiler.CompileMigration() - Roslyn compile
    │       ├─► IMigrationsAssembly.AddMigrations() - Register migration
    │       └─► IMigrator.Migrate() - Apply to database
    │   } catch {
    │       └─► Clean up saved files on failure
    │   }
    │
    └─► Return migration files

Robustness Features

  1. Exception handling with cleanup: AddAndApplyMigration wraps the save-compile-register-apply chain in try-catch, deleting saved files on failure to prevent orphans
  2. Context disposal on validation failure: PrepareForMigration ensures the DbContext is disposed if validation or service building fails
  3. Thread-safe migration registration: MigrationsAssembly uses locking to protect shared state (migrations dictionary, model snapshot, additional assemblies list)

Limitations

  • Requires dynamic code generation (incompatible with NativeAOT) - marked with [RequiresDynamicCode]
  • C# only (no VB.NET/F# support)

Test plan

  • Unit tests for CSharpMigrationCompiler
  • Unit tests for MigrationsOperations.AddAndApplyMigration
  • Integration tests in RuntimeMigrationTestBase (SQLite and SQL Server implementations)
  • Tests for validation (empty name, invalid characters)
  • Tests for RemoveMigration with dynamically created migrations
  • All existing EFCore.Design.Tests pass
  • All existing EFCore.Relational.Tests pass

Fixes #37342

@thromel thromel force-pushed the feature/runtime-migrations branch from a15611a to 9a35a9b Compare December 23, 2025 20:45
@AndriySvyryd AndriySvyryd self-assigned this Dec 23, 2025
@thromel thromel marked this pull request as ready for review December 24, 2025 06:22
@thromel thromel requested a review from a team as a code owner December 24, 2025 06:22
@thromel thromel marked this pull request as draft December 25, 2025 02:03
@thromel thromel force-pushed the feature/runtime-migrations branch from 62018f1 to 5c41f2f Compare December 25, 2025 03:51
@thromel thromel marked this pull request as ready for review December 25, 2025 08:13
@thromel thromel marked this pull request as draft December 25, 2025 16:43
@thromel thromel marked this pull request as ready for review December 25, 2025 21:07
@thromel thromel requested a review from AndriySvyryd December 31, 2025 05:21
@thromel

This comment was marked as outdated.

@roji roji force-pushed the main branch 2 times, most recently from 249ae47 to 6b86657 Compare January 13, 2026 17:46
@thromel thromel requested a review from AndriySvyryd January 16, 2026 18:29
Copy link
Member

@AndriySvyryd AndriySvyryd left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for your contribution!

@AndriySvyryd AndriySvyryd merged commit 0334a65 into dotnet:main Jan 16, 2026
10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Allow to create a migration and apply it without recompiling

2 participants