11

I have an Entity Framework Core + ASP.NET Core application and when my application starts up I want to ensure that the database is created, and eventually (once I have migrations) I want to ensure that those are also run.

Initially I put Database.EnsureCreated() into the constructor of my DbContext but that appears to run every time someone hits my application since a new instance of the DbContext is created each time.

I tried to put it into my startup code, but I need an instance of my DbContext to do that and it is unclear how exactly to get one. I am configuring EF as so:

serviceCollection.AddEntityFramework()
    .AddSqlServer()
    .AddDbContext<Models.MyContext>(options => options.UseSqlServer(...));

I don't see a way to get an instance of the DbContext from the service collection, and I don't see any appropriate singleton to inject a DbContext into so I can do some one-time initialization.

So what is the best place to ensure some code related to my DbContext is called once per application run?

Micah Zoltu
  • 6,764
  • 5
  • 44
  • 72

3 Answers3

6

At the time of this writing, there is not a "correct" place to run code on application startup such that it executes within the request scope (see https://github.com/aspnet/Hosting/issues/373).

For now, the workaround is to do the following, but it won't work in more complex multi-application scenarios (see https://github.com/aspnet/EntityFramework/issues/3070#issuecomment-142752126)

public class Startup
{
    ...

    public void Configure(IApplicationBuilder applicationBuilder, ...)
    {
        ...
        // NOTE: this must go at the end of Configure
        var serviceScopeFactory = applicationBuilder.ApplicationServices.GetRequiredService<IServiceScopeFactory>()
        using (var serviceScope = serviceScopeFactory.CreateScope())
        {
            var dbContext = serviceScope.ServiceProvider.GetService<MyDbContext>();
            dbContext.Database.EnsureCreated();
        }
    }
}
Micah Zoltu
  • 6,764
  • 5
  • 44
  • 72
  • Hey Micah, I agree with you because I also saw that github issue 3070 , but can't you inject `MyDbContext` **directly** into your `Configure()` method? – Nate Anderson May 25 '17 at 12:29
  • 1
    I believe I just copied this from that GitHub issue. It is possible there is a better way, but I assumed that the author of that (being an EF developer) knew what was best and so I just copied their solution. Does Configure run before or after DI is initialized? Perhaps the dbContext you get from `serviceScopeFactory` is somehow different than the one you get from DI? If you find a better solution than this, feel free to submit an edit or a comment here. :) – Micah Zoltu May 25 '17 at 16:35
  • Just checking, I also claim ignorance on this topic :) I've answered in at least two spots ([example](https://stackoverflow.com/questions/34536021/seed-initial-data-in-entity-framework-7-rc-1-and-asp-net-mvc-6/44171698#44171698)) asserting that DI directly into Configure() is how the [intro docs](https://learn.microsoft.com/en-us/aspnet/core/data/ef-mvc/intro) do it, maybe someone will tell me why I'm wrong, or how they are different. Thanks, – Nate Anderson May 25 '17 at 17:42
  • I don't know if this works on earlier version, for me it says MyDbContext not found on DI container. I think this is executed before ConfigureServices, or executed in parallel. – Lee Song Oct 15 '17 at 09:52
4

I wonder why you would run to run EnsureCreated as part of your service anyway. Do you really want your webserver to create or update the database schema? Why would the webserver be up and serving request if the database is not up to date?

Do you really trust your migrations so much that they don't ruin data when executed, that you don't want to test the data after running them?

In addition, this will require you to give the webserver database user permissions to change the database schema. This is a vulnerability in itself - someone taking over your webserver will be able to modify your database schema.

I suggest you create the database and apply migrations in a small utility you run yourself, not as part of your web application.

zmbq
  • 38,013
  • 14
  • 101
  • 171
  • I appreciate the arguments you have made against creating/migrating from within the application. For an enterprise scale application I would tend to agree with you on all points. However, for a small startup sized service, having the create/migrate inside of the application vastly simplifies deployment, testing, migration, management, debugging, etc. and I believe it is worth the additional risks when working towards an MVP. Once the service has grown sufficiently, then one can look into extracting out the DB creation/migration. – Micah Zoltu Apr 30 '16 at 19:41
  • In my situation, my client is Linux based programmer and he wishes to switch database server by just editing the config file. He will be handling cloning and those. Without EnsureCreated he has to install all tools to run Update-Database. – Lee Song Oct 15 '17 at 09:43
  • There's a nice `Migrate` utility that does this. – zmbq Oct 15 '17 at 10:30
  • This is a perfectly valid thing to use in development when using an in-memory DB or during testing. – Douglas Gaskell Dec 29 '18 at 03:55
0

I think zmbq's suggestion is a correct one and there is a way to ensure that migrations are run along with the deployment, so that binaries and database changes are in sync, using Visual Studio's Publish functionality.

When publishing against an IIS instance, one can specify target database connection string to use to also run required migrations:

Entity framework migrations used when publishing

This will ensure that changes are applied only when needed (not every time application starts) and that application runs using the least required database rights (i.e. database writer, reader etc.) as opposed to rights to alter tables, create indexes etc.

Graham
  • 7,431
  • 18
  • 59
  • 84
Alexei - check Codidact
  • 22,016
  • 16
  • 145
  • 164