EF Core - Applying Migrations Programmatically

By FoxLearn 2/6/2025 8:24:55 AM   6
In EF Core, migrations are used to create the database, apply schema changes, and update tables.

While you can apply migrations manually through the command line or tools like Package Manager Console, EF Core also provides methods to apply migrations programmatically via DbContext.Database.

To apply any pending migrations programmatically, you can use:

await context.Database.MigrateAsync();

This method will create the database (if it doesn't exist) and apply all pending migrations.

To check for pending migrations:

var pendingMigrations = await context.Database.GetPendingMigrationsAsync();

To view applied migrations:

var appliedMigrations = await context.Database.GetAppliedMigrationsAsync();

To apply a specific migration:

await context.GetInfrastructure().GetService<IMigrator>().MigrateAsync("Database_v4");

In this article, we’ll go over the benefits of applying migrations programmatically and show examples using EF Core’s migration methods.

Advantages of Applying Migrations Programmatically

Using the dotnet ef command line tool requires you to install the tool and execute commands from the project folder. This can be cumbersome in production environments where developers aren’t directly involved in the deployment process.

Applying migrations programmatically is advantageous because the migration logic resides directly in the deployed code. There’s no need to deploy source code or install extra tools. Plus, if you generate SQL scripts to apply migrations, they don’t handle database creation, only table updates, which can be a limitation.

With the programmatic approach, calling MigrateAsync() will handle both database creation and migration application, making it simpler and less error-prone.

Example of Checking for Pending Migrations and Applying Them Programmatically

Here’s an example that checks for pending migrations, applies them if any are found, and then reports the last applied migration:

using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;

var config = new ConfigurationBuilder()
    .SetBasePath(AppDomain.CurrentDomain.BaseDirectory)
    .AddJsonFile("appsettings.json")
    .AddUserSecrets<Program>()
    .Build();

using (var context = new LibraryContext(config.GetConnectionString("Default")))
{
    var pendingMigrations = await context.Database.GetPendingMigrationsAsync();

    if (pendingMigrations.Any())
    {
        Console.WriteLine($"You have {pendingMigrations.Count()} pending migrations to apply.");
        Console.WriteLine("Applying pending migrations now...");
        await context.Database.MigrateAsync();
    }

    var lastAppliedMigration = (await context.Database.GetAppliedMigrationsAsync()).Last();

    Console.WriteLine($"You're on schema version: {lastAppliedMigration}");
}

This example uses a connection string from a configuration file and checks for pending migrations before applying them. It then reports the last migration that was applied.

How Does EF Core Know Which Migrations Have Been Applied?

EF Core tracks applied migrations using the __EFMigrationsHistory table. You can directly query this table to see the migrations that have been applied:

SELECT [MigrationId] FROM [dbo].[__EFMigrationsHistory];

This query will return a list of migration IDs, like this:

MigrationId
20210101123000_InitialMigration
20210214091520_AddBooksTable
20210318073611_AddAuthorsTable

These are the migrations that have been successfully applied to the database. The last migration in this list represents the current schema version.

Example of Applying a Specific Migration Programmatically

In some cases, you may want to apply a specific migration (for example, rolling back to an earlier schema version). EF Core makes this easy using the IMigrator service.

Let’s assume you’re currently on Database_v6 and need to roll back to Database_v4:

using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.Extensions.DependencyInjection;

var config = new ConfigurationBuilder()
    .SetBasePath(AppDomain.CurrentDomain.BaseDirectory)
    .AddJsonFile("appsettings.json")
    .AddUserSecrets<Program>()
    .Build();

using (var context = new LibraryContext(config.GetConnectionString("Default")))
{
    await context.GetInfrastructure().GetService<IMigrator>().MigrateAsync("Database_v4");

    var lastAppliedMigration = (await context.Database.GetAppliedMigrationsAsync()).Last();

    Console.WriteLine($"You're on schema version: {lastAppliedMigration}");
}

This will apply the migration Database_v4 and output the schema version as:

You're on schema version: 20210316124316_Database_v4

Notice that we only specify the name of the migration ("Database_v4") rather than the full timestamp.