Ways To Run Entity Framework Migrations in ASP.NET Core 6

How to add your migrations to CI/CD pipeline

Bohdan Tron
Geek Culture
6 min readOct 21, 2022

--

Photo by Julia Craice on Unsplash

If you’ve decided to use Entity Framework (Code-First approach) in your next project, sooner or later, you’ll have to make changes to your database via migrations. In this article, we’ll consider how to work with EF migrations in general and how to run them as a part of your DevOps pipeline.

Table of Contents

  1. Web API Project Setup
  2. DbContext & Model
  3. The First Migration
  4. Common Ways To Handle Migrations in CI/CD
  5. Non-Typical Way To Run Migrations
  6. Command Line Parser
  7. Conclusion

Web API Project Setup

Firstly let’s create a new web project using the following .NET CLI commands:

dotnet new sln --name EfMigrationsApp
dotnet new web --name WebApi --framework net6.0
dotnet sln add .\WebApi\WebApi.csproj

Next, let’s go to the WebApi project and add some NuGet packages to it:

cd WebApi
dotnet add package Swashbuckle.AspNetCore --version 6.2.3
dotnet add package Microsoft.EntityFrameworkCore --version 6.0.9
dotnet add package Microsoft.EntityFrameworkCore.Design --version 6.0.9
dotnet add package Microsoft.EntityFrameworkCore.Sqlite --version 6.0.9

Swashbuckle.AspNetCore — we need this package to interact with the future API endpoints via Swagger.

Microsoft.EntityFrameworkCore — will allow us to use Entity Framework’s main features (such as DbContext).

Microsoft.EntityFrameworkCore.Design — will give us an option to work on migrations.

Microsoft.EntityFrameworkCore.Sqlite — a provider for SQLite database. In our test app, we want to get the database ready as fast as possible, and SQLite perfectly fits our needs.

Now let’s tell our app to launch Swagger by default; go to launchSettings.json and add the following line to the WebApi and IIS Express profiles:

"launchUrl": "swagger"

And make Program.cs look this way:

Program.cs

Alright, the project has a basic configuration, so we can move on to setting up the models and DbContext.

DbContext & Model

Firstly let’s create a DataAccess folder and define our model in the following way:

User.cs

To interact with the database, we also need to create a DbContext class like this:

AppDbContext.cs

And let’s register our DbContext with ASP.NET Core; to do this, we need to modify Program.cs the following way:

Program.cs

We’ve configured the context to connect to the SQLite database (lines 9–10). Also, we’ve added a simple endpoint to get all the users from our database (lines 17–22); we will use it slightly later.

As you can see, we retrieve the connection string for the database from the Configuration class, so to make this work, we also need to update the appsettings.json the following way (lines 9–11):

appsettings.json

From this point, we can start adding the migrations to our project.

The First Migration

To manage the migrations, we’ll use dotnet ef tools, so let’s install them by running this in the cmd:

dotnet tool install --global dotnet-ef

Now let’s create our first migration with the initial state of the database. To do this, we need to run the following command:

dotnet ef migrations add Initial

And by the next command, we can apply the migration to our database:

dotnet ef database update

Alright, that should be enough for local development. We can use the commands above to manage our database state. But what if we need to add the migrations to our CI/CD pipeline? Let’s look at possible ways to do this.

Common Ways To Handle Migrations in CI/CD

Generally, there are three ways to handle EF migrations in the DevOps pipeline:

1. SQL Scripts

Entity Framework Core allows us to generate pure SQL scripts based on the migrations. All we need to do is to run the following command:

dotnet ef migrations script

With this in mind, we can create scripts during the Build pipeline, publish them as artifacts, and run them during the Release pipeline. There is a good article on how to do this in Azure DevOps.

2. DevOps-Friendly EF Core Migration Bundles

With the .NET 6, the EF Core team released a new migration bundles feature. The idea is that you can generate an executable file containing everything needed to run migrations. We can create a bundle by running the following command:

dotnet ef migrations bundle --configuration Bundle

As a result, we’ll get the efbundle.exe file that can be executed to apply the migrations to the database. Although this approach might be preferable in some cases, the general algorithm remains the same as for SQL scripts. We still need to produce a file as part of the CI process and execute it as part of the CD.

3. Application Startup

Entity Framework provides us an option to run migrations programmatically by running Database.Migrate() method. Considering this, we can just put that method at the beginning of the Program.cs and run the migrations during the application startup. The code might look the following way:

Program.cs

Although this is the fastest and easiest way to handle migrations, the approach is risky and not recommended for production applications. If multiple application nodes run simultaneously, they may attempt to apply migrations and update the database concurrently, which will cause failures or data corruption.

Non-Typical Way To Run Migrations

In most cases, I would use either SQL scripts or the migration bundles approach as the safest and most reliable. But if we’re not going to review SQL migration scripts before deployment and want to handle migrations only as a part of the CD pipeline, we can do the following:

Program.cs

The idea is to pass the command line argument to specify whether to run migrations. If we pass the “— RunMigrations” parameter while launching the app, it will only need to run migrations and not build the actual web application.

We can say that our application can now run in two modes — as a web and as a console app, and the input arguments dictate the mode.

In console mode, we don’t need to create a web application builder, register services, and do all the other fancy stuff required for a real web app. All we need to do is manually create a DbContext instance, pass it the connection string and run migrations programmatically (lines 6–18).

In the CD pipeline, a PowerShell task of running migrations might look the following way:

dotnet WebApi.dll --RunMigrations

Notice that you should specify the correct path to your WebApi.dll file.

Since that’s a separate step in the pipeline, we’ve mitigated the risks of the application startup approach, but there’s a problem — we use appsettings.json to get a connection string. This works fine locally, but to make it work in all environments, we’d better pass the connection string as an input parameter. The command can look the following way:

dotnet WebApi.dll --RunMigrations --connectionString "Data Source=SQLite.db"

Now let’s figure out how to parse such strings in our app.

Command Line Parser

To handle input parameters, we’ll use CommanLineParser lib, so let’s add it to our project:

dotnet add package CommandLineParser --version 2.9.1

Next, let’s create a CommanLineParser class to parse our arguments:

CommandLineParser.cs

The usage in Program.cs will look the following way:

Program.cs

For a better local development experience, let’s modify launchsettings.json and add a new section to it (lines 30–33):

launchSettings.json

Because of that change, we’ve got a new button in Visual Studio that we can use to run migrations locally.

Let’s seed some initial data via migrations to check how everything works. Firstly let’s update the AppDbContext.cs in the following way:

AppDbContext.cs

Next, let’s create a migration:

dotnet ef migrations add InitialData

After running the migrations via Visual Studio or cmd, let’s launch our app and call the endpoint to get users:

API Response

We see that all the users have been seeded successfully.

Conclusion

Alright, that’s it. Thanks for reading! Please share your thought about the ways described in the article. Also, I would like to know how you handle EF migrations in your project.

You can find the source code on my GitHub.

--

--