Is it possible to have my ASP Core Web API ensure the DB is migrated to the latest migration using EF Core? I know this can be done through the command line, but I want to
I followed the IStartupFilter
approach to have a generic way for migrating any context.
public class DataContextAutomaticMigrationStartupFilter<T> : IStartupFilter
where T : DbContext
{
/// <inheritdoc />
public Action<IApplicationBuilder> Configure(Action<IApplicationBuilder> next)
{
return app =>
{
using (var scope = app.ApplicationServices.CreateScope())
{
scope.ServiceProvider.GetRequiredService<T>().Database.SetCommandTimeout(160);
scope.ServiceProvider.GetRequiredService<T>().Database.Migrate();
}
next(app);
};
}
}
Now we're able to register the DataContexts and migration in the following way:
1st context
services.AddDbContext<ConsumerDataContext>(options => options.UseSqlServer(configuration.GetConnectionString("ConsumerConnection")), ServiceLifetime.Transient);
services.AddTransient<IStartupFilter, DataContextAutomaticMigrationStartupFilter<ConsumerDataContext>>();
2nd context
services.AddDbContext<UserDataContext>(options => options.UseSqlServer(configuration.GetConnectionString("UserConnection")), ServiceLifetime.Transient);
services.AddTransient<IStartupFilter, DataContextAutomaticMigrationStartupFilter<UserDataContext>>();
..and so on..
The culprit of IStartupFilter
is that it only allows synchronous execution of code. For database migrations this is not an issue since we have a synchronous Migrate()
method.
Starting .NET Core 2 using C# 7.1, you can have an asynchronous Main
method to your app, so you can call all initialization logic before you run the host, right after it has finished building:
public class Program
{
public static async Task Main(string[] args)
{
//first build
var host = CreateHostBuilder(args).Build();
//initialize
using (var serviceScope = host.Services.CreateScope())
{
var serviceProvider = serviceScope.ServiceProvider;
var isDevelopment =
serviceProvider.GetRequiredService<IWebHostEnvironment>().IsDevelopment();
using var context = serviceProvider.GetRequiredService<AppDbContext>();
if (isDevelopment)
await context.Database.EnsureCreatedAsync();
else
await context.Database.MigrateAsync();
if (isDevelopment)
{
using var userManager =
serviceProvider.GetRequiredService<UserManager<AppUser>>();
await userManager
.CreateAsync(new AppUser { UserName = "dummy", Email = "dummy@dumail.com" },
password: "1234");
}
}
//now run
host.Run();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
}
A note from documentation on the call to db.Database.EnsureCreated()
:
Note that this API does not use migrations to create the database. In addition, the database that is created cannot be later updated using migrations. If you are targeting a relational database and using migrations, you can use the DbContext.Database.Migrate() method to ensure the database is created and all migrations are applied.
You may just want to call db.Database.Migrate()
.
Comment taken from source found above declaration here.
This is a slight correction to the previous answer which created an extension method. It fixes the error that is thrown the way it was written.
using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.AspNetCore.Builder;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
namespace MyApp.Extensions
{
public static class IApplicationBuilderExtensions
{
public static void SyncMigrations<T>(this IApplicationBuilder app) where T : DbContext
{
using (var serviceScope = app.ApplicationServices.GetRequiredService<IServiceScopeFactory>().CreateScope())
{
var context = serviceScope.ServiceProvider.GetService<DbContext>();
context.Database.Migrate();
}
}
}
}
This works for me in ASP.NET Core 3.1, simply injecting the db context as a parameter to the Configure
method after registering it in the ConfigureServices
method.
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<DataContext>(x => x.UseSqlite("Data Source=LocalDatabase.db"));
...
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, DataContext dataContext)
{
dataContext.Database.Migrate();
...
}
More details and links to full code samples available at https://jasonwatmore.com/post/2019/12/27/aspnet-core-automatic-ef-core-migrations-to-sql-database-on-startup