I wanted to migrate databases during a release. The default way of doing this is to run code when the application starts. Great for little demo projects but not when you are releasing a real app to production. When you are pushing a new version to production and the migration fails the release should fail, alarms should go off etc. With the “old” Entity Framework I usually wrapped the migration code in a console application. I packaged it alongside the other code and then executed an additional release step to execute the migrations. Since I’ve been using .NET Core on my latest projects I wanted to see if I could use all the default tooling.
If you run the update command with the verbose option you will see that “dotnet exec” is called using “EF.dll” together with a bunch of other options. This is the behaviour I want to replicate during a release.
dotnet ef database update -v |
The first thing to figure out is where to find “EF.dll”. This is of course in a NuGet package. By default they are downloaded in your user’s home folder. I was not able to figure out a way to just grab it from there using a hosted build agent. However by modifying the restore command I can specify where the packages need to be downloaded.
dotnet restore --packages /my/location |
I then added an additional “Archive files” build step to my build definition. This step creates a zip file and adds it to the build artifacts staging directory. This means it will automatically be picked up with all the other deliverables.
Those are all the necessary changes for the build definition. Now the extra bits in the release definition. I need four additional steps, but it’s also important to select the correct agent. As I’ll be running .NET Core commands I need a VS2017 agent.
The first step is to unarchive the archive which has the appsettings.json with the connectionstring, in my case it’s the output of a project which houses the web api and an Angular app, the other archive we need has the EF Core bits we need.
I then need the correct connectionstring, this is of course a variable in my release environment. The easiest way to replace the connectionstring in appsettings.json of the unarchived files of the web application is to use an extension of VSTS. It’s a two step process, we first tokenize the file so the second step can find the placeholders.
The final step is to do the same command we execute on our development machine. The hard part is figuring out all the parameters. In my case the migrations (and all of the logic) is in a dll named: App.dll while the web application and the connectionstring is from another dll: App.Web.dll
exec --depsfile App.Web.deps.json --runtimeconfig App.Web.runtimeconfig.json ef.dll database update --assembly App.dll --startup-assembly App.Web.dll --verbose --root-namespace MyAppNamespace |
And that’s it, now the database is upgraded during a release.
I tried this approach w/ EF Core 2. The paths have changed. The EF stuff is now under a .tools folder, but where I couldn’t get it to work was in the netcoreapp2.0 folder, there is no ef.dll, only microsoft.entityframeworkcore.tools.dotnet.cache.
I tried yesterday to get it going with 2.0, no success yet.
I figured out a way to do it. Basically it means to download the package manually. I’ll write about it.
good article – will try it out soon…
one question still: the deployment consists of two independent steps: DB upgrade and App deploy
How to achieve a deployment atomicy? Writing additional code to try/catch/rollback first part is not fun really…
But leaving an environment in a partly upgraded (read: not-working) state is not an option at all.
Keeping every deploy under deep manual control is boring and time-consuming
Any ideas, guys?
Igor, take a backup before doing anything. Perform upgrade, if it succeeds you are ok, if not put the backup back. This can be automated as well.
Hi @bricelam , I have gone through all of your examples and solutions pertaining to above issue being discussed and they are really helpful since I am also trying to execute migrations against a dll, or a class library compiled to a dll, but unfortunately I have still not been able to implement or adapt your solution above . I would really appreciate if you can please go into more details and explain your approach. Following were the issues I encountered :
1) “exec” statement didn’t work
2) which deps.json and runtimeconfig.json are you referring to and how do you package it together in the final archive
3) the ef.dll that is being copied/packaged in my project is still for .net core2.0 even though my csproj imports packages >=2.1.4 for .net core 2.1, which causes the same error as above “Your startup project doesn’t reference Microsoft.EntityFrameworkCore.Design”
4) my project, containing the dbcontext, and the startup project, which contains the dbcontextfactory and migrations, are separate
5) I want to trigger migrations during install time depending upon user input and whether db exists or not and needs to be upgraded/downgraded, via migrations, or removed and created fresh
thanks in advance