Entity Framework Core’s code-first approach makes schema management easy with the migrations feature. Just create your models in C# code, then use a simple command to generate the migrations:
dotnet ef migrations add InitialMigration
When you are ready to apply those migrations to your database, there’s another command:
dotnet ef database update
Your database gets brought up to the latest version–whether that means applying just the most recent migration or two or creating the entire schema.
EF Core Migrations in Azure DevOps: The Problem
Given that Microsoft is responsible for creating this support, you might think that the Azure DevOps build and release pipelines would have great support for EF migrations and it would be simple to drop migrations right in. However, the ‘dotnet ef’ command builds the source so it can apply the database updates. This would be fine if we want to apply the database migrations in the build pipeline. But we don’t want to do that–database update is a deployment activity and belongs in our release pipeline.
There are two problems with that… First, we don’t have ready access to the source in the release pipeline. We would need to package up the source as an artifact of the build so we could pass it over to the the release pipeline. The second problem, that I learned the hard way, is that even with the source available in the release pipeline, building the code there is not quite the same as building in the build pipeline. For example, if you need to access a private NuGet feed within your project, it does not authenticate for you like in a build pipeline.
Create SQL Script in Build Pipeline
I went through several different approaches to handling this until I settled on what I think is the cleanest: have your build process generate the migration scripts, package them as artifacts, and run them at deployment time.
My build is run under psake and here is the relevant line:
dotnet ef migrations script --output $stagingDir\SQL\$scriptName --context $dbContext --idempotent
$stagingDir is my artifact staging location.
$scriptName is the name of SQL script we will run during deployment.
$dbContext is the class name of the DbContext (without namespacing).
Note the –idempotent flag. This directs the migration code to generate a script that can be run on an existing database and only apply any missing migrations.
Run Script in Release Pipeline
Then, over in my release pipeline add step to run a SQL scripts. My database is Azure SQL:
Set this task up to find the SQL script we created in the build and run it against the corresponding database.
While it’s not as automatic as we might have liked, running EF Core migrations in Azure DevOps is still pretty easy. This approach would work just as well with any combination of build and release tools such as Team City, Octopus Deploy, etc.