89

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 do it programatically.

mattmanser
  • 5,719
  • 3
  • 38
  • 50
Zeus82
  • 6,065
  • 9
  • 53
  • 77
  • still not done it should be implemented with the next version. You can use the workaround which is posted below from Khan. – Bassam Alugili Jun 13 '16 at 10:59
  • 1
    See the answers below. You should use either EnsureCreated or Migrate. Not both. – Matt Varblow Oct 13 '17 at 13:10
  • 13
    Official documentation says : 'Don't call EnsureCreated() before Migrate(). EnsureCreated() bypasses Migrations to create the schema and cause Migrate() to fail.' : https://learn.microsoft.com/en-us/ef/core/managing-schemas/migrations/#applying-migrations-at-runtime – Oswin Jun 04 '18 at 16:28

13 Answers13

58

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.

steamrolla
  • 2,373
  • 1
  • 29
  • 39
52

You can use

db.Database.EnsureCreated();

to get your db up to date with your current model. If you want to enable migrations (If subsequent migrations are suspected), then use

db.Database.Migrate();

and put your subsequent migrations over time.

bricelam
  • 28,825
  • 9
  • 92
  • 117
Janshair Khan
  • 2,577
  • 4
  • 20
  • 44
29

Use below code to run migration at

public async void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
    using (var serviceScope = app.ApplicationServices.GetRequiredService<IServiceScopeFactory>().CreateScope())
    {
        var context = serviceScope.ServiceProvider.GetService<YourContext`enter code here`>();
        context.Database.Migrate();
    }
}
Bidou
  • 7,378
  • 9
  • 47
  • 70
chintan310
  • 334
  • 3
  • 10
  • 3
    After upgrading from .NET Core 2.1 to 3.0 (and implicitly from out-of-process hosting to in-process hosting), calling `Migrate` from the startup stopped working for me. The web app just wouldn't boot without error message. I had mark the project as `OutOfProcess` and then it worked again. – Dejan Oct 29 '19 at 13:49
19

Based on the answer of @steamrolla I would propose the following improvement:

public static class EnsureMigration
{
    public static void EnsureMigrationOfContext<T>(this IApplicationBuilder app) where T:DbContext
    {
        var context = app.ApplicationServices.GetService<T>();
        context.Database.Migrate();
    }
}

With this you can also ensure the migration of different contexts, e.g. if you have a Identity database.

Usage:

app.EnsureMigrationOfContext<context>();
pisker
  • 716
  • 6
  • 8
18

This works for me in ASP.NET Core 3.1, simply injecting the db context as a parameter to the existing 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

live2
  • 3,771
  • 2
  • 37
  • 46
Jason Watmore
  • 4,521
  • 2
  • 32
  • 36
  • This works if you only have one or few contexts. But if you have many contexts there should be another way – Sven Feb 03 '20 at 11:25
  • It works with multiple contexts as well. I use it in a project with multiple contexts that inherit from a single base context - https://jasonwatmore.com/post/2019/10/14/aspnet-core-3-simple-api-for-authentication-registration-and-user-management – Jason Watmore Feb 04 '20 at 03:10
  • I mean it's not very convenient to add multiple context parameters in the `Configure()` method. I followed the `IStartupFilter` approach to get migration automatically done on app startup. – Sven Feb 04 '20 at 07:30
  • Only a single context parameter is required in the `Configure()` method - https://github.com/cornflourblue/aspnet-core-3-registration-login-api/blob/master/Startup.cs#L90 It maps to a different concrete type depending on the environment - https://github.com/cornflourblue/aspnet-core-3-registration-login-api/blob/master/Startup.cs#L33-L36 – Jason Watmore Feb 04 '20 at 08:51
  • Ok I see. Your use case is different. You only have 1 context but with two different forms. I think that's a special behavior. In general speaking, one would like to automatically migrate all datacontexts within an application on app startup. For instance, we currently have 9 datacontexts connecting to 4 different databases (all SQL server). – Sven Feb 04 '20 at 10:40
  • Ah yep that's a different use case – Jason Watmore Feb 05 '20 at 05:30
6

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.

Sven
  • 2,345
  • 2
  • 21
  • 43
  • `.SetCommandTimeout(160);` is that temporary or will that persist? – sommmen Mar 22 '22 at 12:33
  • @sommmen While reading the doc (https://learn.microsoft.com/en-us/dotnet/api/microsoft.entityframeworkcore.relationaldatabasefacadeextensions.setcommandtimeout?view=efcore-6.0) this settings is persistent throughout the DbContext. – Sven Mar 24 '22 at 13:11
  • i thought so - in that case perhaps its not good te set it here by default in that case... – sommmen Mar 24 '22 at 13:32
  • @sommmen feel free to modify code samples according to your needs. – Sven Apr 26 '22 at 07:10
5

This code works in .NET core 3.0

 using (var scope = app.ApplicationServices.CreateScope())
 {
     var dbContext = scope.ServiceProvider.GetService<T>();
     dbContext.Database.Migrate();
 }
Igor
  • 1,835
  • 17
  • 15
5

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>();
      });
}
Shimmy Weitzhandler
  • 101,809
  • 122
  • 424
  • 632
4

Based on chintan310's answer, here is how I migrate the database. This ensures separation of database-related tasks into Program.cs:

    public static void Main(string[] args)
    {
        var host = BuildWebHost(args);

        using (var scope = host.Services.CreateScope())
        {
            var services = scope.ServiceProvider;

            try
            {
                var context = services.GetService<AppDbContext>();
                context.Database.Migrate();

                var seeder = scope.ServiceProvider.GetService<AppSeeder>();
                seeder.Seed().Wait();
            }
            catch (Exception ex)
            {
                var logger = services.GetRequiredService<ILogger<Program>>();
                logger.LogError(ex, "An error occurred seeding the DB.");
            }
        }

        host.Run();
    }

    private static IWebHost BuildWebHost(string[] args) =>
        WebHost.CreateDefaultBuilder(args)
            .UseStartup<Startup>()
            .Build();
umutesen
  • 2,523
  • 1
  • 27
  • 41
4

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<T>();
                context.Database.Migrate();
            }
        }
    }
}
user2771704
  • 5,994
  • 6
  • 37
  • 38
Dblock247
  • 6,167
  • 10
  • 44
  • 66
  • Like this approach, just small correction, there should be `var context = serviceScope.ServiceProvider.GetService();` instead of `` – user2771704 May 25 '22 at 12:18
4

In Asp core 6 you dont have StartUp , in Previous version of asp we had Configure method wich allow up to access ServiceProvider directly and then we can use GetServices to get DBcontext and then call Migrate Methods.

but now in Asp core 6 . we should create a scop and then get DBcontext object

        using (var Scope = app.services.CreateScope())
        {
            var context = Scope.Services.GetRequireService<DBContext>();
            context.Database.Migrate();
        }
  • 1
    using (var Scope = app.Services.CreateScope()) { var context = Scope.ServiceProvider.GetRequiredService(); context.Database.Migrate(); } – pyRabbit Apr 09 '22 at 14:20
2

In EF Core 7 you can do it in Program.cs right after var app = builder.Build(); this way :

using (var Scope = app.Services.CreateScope())
{
    var context = Scope.ServiceProvider.GetRequiredService<AppDbContext>();
    context.Database.Migrate();
}

Happy coding and don't forget Up Vote ;)

Ali Mahmoodi
  • 858
  • 10
  • 14
1

I did this to migrate programmatically with EF Core 2.1.2 & SQL Server, based on previous answers here and bailando bailando's answer on "How and where to call Database.EnsureCreated and Database.Migrate?":

Startup.cs

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.EntityFrameworkCore;

namespace MyApp
{
    public class Startup
    {
        // ... (only relevant code included) ...

        public void ConfigureServices(IServiceCollection services)
        {
            services.AddDbContext<MyAppContext>(options => 
                options.UseSqlServer(Configuration.GetConnectionString("MyAppContext")));
            // ...
        }

        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            using (var serviceScope = app.ApplicationServices.CreateScope())
            {
                var context = serviceScope.ServiceProvider.GetService<MyAppContext>();
                context.Database.Migrate();
            }
            // ...
        }
    }
}

The project using this code is available at Github.

Lauri Harpf
  • 1,448
  • 1
  • 12
  • 30