EF Core – Apply migrations programmatically

In EF Core, you create migrations when you are first creating the database and tables and also for any database schema changes. DbContext.Database has a few methods you can call to manage migrations programmatically.

To apply any pending migrations:

await context.Database.MigrateAsync();
Code language: C# (cs)

If the database doesn’t exist, MigrateAsync() will create it and then apply the migrations.

To check if there are any pending migrations:

var pendingMigrations = await context.Database.GetPendingMigrationsAsync();
Code language: C# (cs)

To check which migrations have been applied:

var appliedMigrations = await context.Database.GetAppliedMigrationsAsync();
Code language: C# (cs)

To apply a specific migration:

await context.GetInfrastructure().GetService<IMigrator>().MigrateAsync("Database_v4");
Code language: C# (cs)

This migrates up or down to the specified migration, depending which migration you’re currently on.

There are a few other ways to apply migrations, but applying them programmatically has some advantages. In this article, I’ll explain these advantages and show examples applying migrations programmatically.

Note: There are also non-async versions of the migration API methods shown above.

Advantages of applying migrations programmatically

If you use the dotnet ef command line tool, then you have to have the dotnet ef tool installed and you have to execute it from the project folder. That’s not feasible in a non-dev environment (who wants to go around deploying their project source code?). Note: The same applies to using the Package Manager Console tools.

With the programmatic approach, the logic is in the deployed code itself. You don’t need to deploy the project source code or deal with installing command line tools and executing separate commands.

Another way to apply a migration is to generate SQL scripts from the migration and then execute the scripts. This is definitely not desirable in a dev environment. It may be a good approach in an automated CI/CD pipeline though. The other problem with this approach is the generated SQL scripts don’t contain the database creation logic, just the table creation logic. So you will have to create that separately.

With the programmatic approach, calling MigrateAsync() creates the database if it doesn’t exist. Furthermore, you don’t need to worry about moving around SQL scripts and executing them. In other words, it’s much simpler.

In the end, carefully consider which approach is suitable for your situation. You may even want to use different approaches for different environments.

Example of checking for pending migrations and applying them programmatically

The following code checks for pending migrations. If there are any, it executes MigrateAsync() to apply them. Finally, it reports the last migration applied.

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 StreamingServiceContext(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}");
}
Code language: C# (cs)

Note: This is fetching the database connection string from a User Secrets file (which gets loaded into appsettings.json).

DbContext.Database.GetAppliedMigrationsAsync() returns a list of migration names that have been applied. Since migrations are built on top of each other and applied sequentially, the last migration in the list is your current database schema version.

How does EF Core know which migrations have been applied?

In other words, where does context.Database.GetAppliedMigrationsAsync() get the list of applied migrations?

It gets this information from the __EFMigrationsHistory table. You can query this table and get the same information EF Core uses:

SELECT [MigrationId] FROM [dbo].[__EFMigrationsHistory]
Code language: SQL (Structured Query Language) (sql)

This returns the following data:

MigrationId
20210314133726_Database_v0
20210315113855_Database_v1
20210316112804_Database_v2
20210316123742_Database_v3
20210316124316_Database_v4
20210317120015_Database_v5
20210317122744_Database_v6
Code language: plaintext (plaintext)

This means seven migrations have been applied: Database_v0 through Database_v6. The last migration in the list is the current schema version of the database – Database_v6.

Example of applying a specific migration programmatically

You may have noticed that DbContext.Database.MigrateAsync() has no parameters. What if you want to apply a specific migration? You can use the IMigrator object to do this.

For example, let’s say you’re currently on Database_v6 and want to migrate down to Database_v4. Here’s how you’d do that by using the IMigrator object:

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

static async Task Main(string[] args)
{
	var config = new ConfigurationBuilder()
		.SetBasePath(AppDomain.CurrentDomain.BaseDirectory)
		.AddJsonFile("appsettings.json")
		.AddUserSecrets<Program>()
		.Build();


	using (var context = new StreamingServiceContext(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}");
		
	}
}
Code language: C# (cs)

This outputs the following message:

You're on schema version: 20210316124316_Database_v4Code language: plaintext (plaintext)

It successfully migrated down to Database_v4.

Notice that I didn’t have to specify the timestamp, just “Database_v4.”