Running EFCore Migrations From Your Own Code

So anyone who uses Entity Framework/EF Core knows that you can run migrations from powershell/package manager console like so :

Update-Database

And you probably also know that with EF Core, you can also use the dotnet ef command like so :

dotnet ef database update

But in rare cases, you may have a need to run migrations on demand from your C#/.NET Core code, possibly by calling an API endpoint or from an admin screen. This was generally more of an issue in older versions of Entity Framework that had real issues with the “version” of the database versus the “version” that the EF Code thought it should be. Infact, put simply, it would bomb out.

In EF Core, this is less of an issue as your code won’t die until it actually tries to do something that it can’t complete (e.g. Select a column that doesn’t exist yet). But there are still some cases where you want to deploy code, test it works in a staging environment against the live database, *then* run database migrations on demand.

Migrating EF Core Database From C#

It’s actually very simple.

var migrator = _context.Database.GetService<IMigrator>();
await migrator.MigrateAsync();

Where _context is simply your database context. That’s it! Crazy crazy simple!

Checking Pending Migrations

It can also be extremely handy checking which migrations need to be run before attempting to run them. Even then, it can be useful to know which state the database is in from an admin panel or similar just to diagnose production issues. For example, if you roll a manual process of updating the production database, it can be useful to see if it’s actually up to date.

await _context.Database.GetPendingMigrationsAsync()

Really simple stuff.

Migrating EF Core On App Startup

In some cases, you really don’t care when migrations are run, you just want them to migrate the database when the app starts. This is good for projects that the timing of the database migration really doesn’t matter or is an incredibly small rollout window. For example, a single machine of a low use web app probably doesn’t need all the bells and whistles for a separate database rollout, it just needs to be on the latest version at any given time.

For that, .NET Core has this new paradigm of a “StartupFilter”. The code looks like so :

public class MigrationStartupFilter<TContext> : IStartupFilter where TContext : DbContext
{
    public Action<IApplicationBuilder> Configure(Action<IApplicationBuilder> next)
    {
        return app =>
        {
            using (var scope = app.ApplicationServices.CreateScope())
            {
                foreach (var context in scope.ServiceProvider.GetServices<TContext>())
                {
                    context.Database.SetCommandTimeout(160);
                    context.Database.Migrate();
                }
            }
            next(app);
        };
    }
}

Startup Filters in .NET Core are basically like Filters in MVC. They intercept the startup process and do “something” before the application starts, and only on startup. I actually haven’t made much use of them in the past but recently I’ve found them to be incredibly handy. If you ever made use of the global.asax startup methods in Full Framework .NET, then this is pretty similar.

We can then add our this filter to our startup pipeline by editing our startup.cs file like so :

services.AddTransient<IStartupFilter, MigrationStartupFilter<Context>>();

Where Context is our database context. Again, super simple stuff but something that you’ll probably end up using in every new project from now on!

2 thoughts on “Running EFCore Migrations From Your Own Code”

  1. When running migrations as part of the application startup it seems you have the possibility of a race condition with multiple app servers coming online at the same time. Have you seen that?

    Reply
    • 100%. In those cases I generally create an endpoint that I can call manually after deploying all app servers (Or half if you can predict which app server you will hit), to migrate the database (Still using the above code however).

      Reply

Leave a Comment