Recently I’ve been playing around with Entity Framework Core and it’s been a great positive experience overall, however as I started to port one of my projects over I fell foul of the lack of Seeding support. For those that haven’t used the Seed functionality in EF 6, it’s basically a method to populate the database with data that is invoked when migrations finish. There is a open GitHub issue regarding seeding support in EF Core.
My requirements are simple I’d like to use the dotnet CLI to run environment specific Migrations and Seeding from my continuous deployment pipeline. In this post I’m going to show you how I got environment specific Migrations and Seeding working. There are a couple of things that work differently that we need to workaround but that’s fine.
- The first problem to solve is the lack of seeding support, this is easily solved and not an issue at all. You can use a empty migration and seed the data in the migration, this is a great because the seed is ran once if I need to update the data I create a new migration. We also benefit from being able to roll back any seed migrations.
- The second problem I came across is that targeting class library projects for the update-database command is not supported. No problem I created a console app and problem solved.
- The third problem to solve is how to get the environment switch from ef database update. This is what took a little bit of work – it turns out that currently EF Core tools are geared around a ASP.NET project hence not supporting targeting class library projects, running migrations on web start isn’t something I wanted to do for my production apps.
Reviewing the EF Core environment feature commit (which is one of the benefits of open source) confirmed that EF tools are looking for a Startup class and the switch is set via the injected IHostingEnvironment. This is now a problem because DI isn’t supported anymore for console apps.
My first attempt was to pull the EnvironmentName directly from the environment variable turns out this won’t work as it is only set for the current cmd session. It was feeling like I was hitting friction so I changed direction and tried using WebHostBuilder instead, and bingo it worked, it turns out that you can have a Startup class and call Build() but you don’t have to call Run() so you don’t end up running Kestrel by mistake.
Below is a gist of the working solution with some commentary:
Here I’m just bootstrapping our Startup class but notice I’m not calling Run().
In the Startup ctor I take IHostingEnvironment and set it to a private variable, then when ConfigureServices is called I set a Environment Variable for future use. Note that if “Development” is passed as the environment we default to “local”, this is because “Development” is the default and I wanted to avoid using the default in CD.
DbContext changes are quite simple if a database connectionstring is passed via OnConfiguring then we return. Otherwise if no connectionstring is passed we set up the config using the environment variable we set in startup, it’s this code that will be called during a migration.
Here is an example migration notice that DalSoftDbContext parameterless ctor is called allowing OnConfiguring (as described above) to use the environment config that was set using the environment variable.
Here is my project.json for reference but nothing special going on here.
Now all my migrations using the dotnet CLI use the correct appsettings for example for appsettings.production.json:
dotnet ef --project ../DalSoft.Data --startup-project ../DalSoft.Data database update --environment production