Raymond Raymond

EntityFramework Core Database Migration in Azure DevOps Pipeline

event 2021-04-05 visibility 8,909 comment 6 insights toc
more_vert
insights Stats
EntityFramework Core Database Migration in Azure DevOps Pipeline

EntityFramework Core is a commonly used framework in .NET Core applications to access and manage data in a database like SQL Server. When using code-first approach, migration scripts are generated using CLI tool and then database update command is used to apply the changes to databases. To perform database migration in Azure DevOps pipeline, it is feasible with add-ons.

About EF Core CLI

In development environment, we usually follow these steps to apply database changes in code-first EntityFramework projects:

  • Create DbContext and entity classes.
  • Use dotnet ef migrations add sub command to generate migration class. There are other sub commands available to list, remove or script SQL statements: 
    dotnet ef migrations
    
    
    Usage: dotnet ef migrations [options] [command]
    
    Options:
      -h|--help        Show help information
      -v|--verbose     Show verbose output.
      --no-color       Don't colorize output.
      --prefix-output  Prefix output with level.
    
    Commands:
      add     Adds a new migration.
      list    Lists available migrations.
      remove  Removes the last migration.
      script  Generates a SQL script from migrations.
    The following is one example migration script which increases UserAgent field from varchar(150) to varchar(250):
    public partial class IncreaseUserAgent : Migration
        {
            protected override void Up(MigrationBuilder migrationBuilder)
            {
                migrationBuilder.AlterColumn<string>(
                    name: "UserAgent",
                    schema: "flex",
                    table: "FlexActions",
                    type: "nvarchar(250)",
                    maxLength: 250,
                    nullable: true,
                    oldClrType: typeof(string),
                    oldType: "nvarchar(150)",
                    oldMaxLength: 150,
                    oldNullable: true);
            }
    
            protected override void Down(MigrationBuilder migrationBuilder)
            {
                migrationBuilder.AlterColumn<string>(
                    name: "UserAgent",
                    schema: "flex",
                    table: "FlexActions",
                    type: "nvarchar(150)",
                    maxLength: 150,
                    nullable: true,
                    oldClrType: typeof(string),
                    oldType: "nvarchar(250)",
                    oldMaxLength: 250,
                    oldNullable: true);
            }
        }
  • Call dotnet ef database update sub command to apply the changes to the database. 

This approach works well in a development environment and you can even directly follow the same steps to apply the changes to a production Azure SQL Database on cloud. However if you want to automate the whole approach and add control points to your release processes, you can build into your Azure DevOps pipelines. 

infoThe following steps use Azure DevOps portal to implement the pipelines. You can also directly use YAML. 

Azure DevOps pipeline design

In Azure DevOps pipeline, we can add a task to generate database migration SQL script. The generated scripts will be included in the artifact. Then in release pipeline, we can use Azure SQL Database deployment task to run the generate SQL script to deploy the changes to your targeted databases on Azure. 

The high-level flow architect looks like the following diagram:

20210407115507-image.png

Add Entity Framework Core Migrations Script Generator

First, we need to install an extension to your Azure DevOps project from Visual Studio Marketplace:

Entity Framework Core Migrations Script Generator - Visual Studio Marketplace

infoAIf you are familiar with dotnet ef migrations script command, you can directly use it to generate a migration SQL script file without relying on this extension. For simplicity, we just use this extension to do the same. 

Once the extension is installed, we can use it in Azure pipelines.

Navigate to your Azure Pipelines and add all the configurations for this task:

20210406211528-image.png

Make sure you specify the right values for Startup Project Path and DbContexts. In Advanced section, ensure dependencies option is checked:

20210407140658-image.png

You don't need to specify the version as it will automatically pick up the latest .NET Core version. Please ignore .NET Core 3 text if you are using .NET 5 as the author of this extension has not updated it to .NET 5 but it does work for .NET 5 too.

About the published artifacts

When the build is successful, the following logs will be printed out based on my settings:

Writing 'D:\a\1\a/migrations/ApplicationDbContext.sql'...

Generating migration script completed!

The published artifact look like the following screenshot:

20210407135746-image.png

*For my project, the artifact includes the built ASP.NET Core application and the database migration script for ApplicationDbContext class. The latter will be used to apply changes to Azure SQL database. 

The generated migration SQL scripts will be based on all the migration C# classes.  It automatically adds a check to see if each migration was already applied to database previously. The following code snippet is extracted from Kontext database migration script:

BEGIN TRANSACTION;
GO

IF NOT EXISTS(SELECT * FROM [__EFMigrationsHistory] WHERE [MigrationId] = N'20210407120738_AddBlockReason')
BEGIN
    ALTER TABLE [flex].[FlexSites] ADD [BlockReason] nvarchar(1024) NULL;
END;
GO

IF NOT EXISTS(SELECT * FROM [__EFMigrationsHistory] WHERE [MigrationId] = N'20210407120738_AddBlockReason')
BEGIN
    ALTER TABLE [flex].[FlexContents] ADD [BlockReason] nvarchar(1024) NULL;
END;
GO

IF NOT EXISTS(SELECT * FROM [__EFMigrationsHistory] WHERE [MigrationId] = N'20210407120738_AddBlockReason')
BEGIN
    ALTER TABLE [flex].[FlexComments] ADD [BlockReason] nvarchar(1024) NULL;
END;
GO

IF NOT EXISTS(SELECT * FROM [__EFMigrationsHistory] WHERE [MigrationId] = N'20210407120738_AddBlockReason')
BEGIN
    INSERT INTO [__EFMigrationsHistory] ([MigrationId], [ProductVersion])
    VALUES (N'20210407120738_AddBlockReason', N'5.0.3');
END;
GO

COMMIT;

This section basically checks whether migration 20210407120738_AddBlockReason is applied to database. If not, it will then apply the changes to the database and then add the migration history log to table [dbo].[__EFMigrationsHistory] in the target database.

At the moment, Kontext production database only has the following migrations, thus the delta scripts will be executed against the database to add a new filed named BlockReason to three tables.  Once that is done, a new migration history record will be added to table table [dbo].[__EFMigrationsHistory]. Next time when the process runs, nothing will be applied until there is new migrations are added. 

20210407141202-image.png

warning To avoid unexpected changes, it is always a good practice to review the generated scripts before releasing it to production; and it is also a good practice to add a step to backup your target databases before applying migration scripts to the database. 

Execute migration script in release pipeline

In your Azure DevOps release pipeline, add a task from task template Azure SQL Database deployment. Configure this task accordingly as the following screenshot shows:

20210406212219-image.png

For task property SQL Script, ensure you pickup the right file path.  For example, the following value specifies the generated migration script for ApplicationDbContext class. The path is consistent with the build task.

$(System.DefaultWorkingDirectory)/**/drop/migrations/ApplicationDbContext.sql

For this task, it invokes SQLCMD command tool to execute the SQL statements against Azure SQL database. 

Once the task is completed successfully,  the logs looks like the following screenshot:

20210407145827-image.png

Security considerations

As you've noticed in the above approaches, there are several security related items you need to consider before adopting this approach:

  • Database credential or connection string is required when deploying to Azure SQL databases.  You can create variables and set them as secrets or use integrated security authentication. 
  • Access to Azure SQL database is required from Azure DevOps agents. The task will add firewall exception directly using ARM when running the script and it will then remove the firewall exception once it is done. 

If you have other better approach to automate EntityFramework Core code first migrations to databases using Azure DevOps, feel free to make a comment here.

More from Kontext
comment Comments
Raymond Raymond #1681 access_time 2 years ago more_vert

Thanks for pointing this out, Tomek.

I have not used stored procedures in my projects thus cannot comment on that experience at the moment.

Your workaround will definitely work but then it relies on a separate application host in CD pipeline and also the changes will not be visible directly without looking into C# code. Another workaround I can think of is that you could directly keep your stored procedure scripts in separate folders and concatenate them with these generated scripts to form your migration SQL scripts. But obviously you won't be able to easily migrate up or down compared with your approach unless we implement the script versions management function, which itself can be complex. Thus overall, I agree that your approach is a good approach to handle these problems. 

If you want more advance features, there are also many open source and commercial software that can probably manage the schema migrations better if your project is complex. 

format_quote

person Tomek access_time 2 years ago

This approach has limitations. If you use migrationBuilder.Sql(string) method e.g. to update stored procedure in a migration migrations.sql file will be syntactically incorrect and some changes will be not applied. After attempting several workarounds I dropped this method completely and instead of generating sql script I use a small command line app that has about 5 lines and executes context.Database.Migrate() method. It does not crash on stored procedures and logging is better than with sqlcmd. 

T Tomek Swiecicki #1680 access_time 2 years ago more_vert

This approach has limitations. If you use migrationBuilder.Sql(string) method e.g. to update stored procedure in a migration migrations.sql file will be syntactically incorrect and some changes will be not applied. After attempting several workarounds I dropped this method completely and instead of generating sql script I use a small command line app that has about 5 lines and executes context.Database.Migrate() method. It does not crash on stored procedures and logging is better than with sqlcmd. 

Raymond Raymond #1620 access_time 3 years ago more_vert

It depends on:

  • Whether your portals and functions share the same database?
  • Whether you are using a single project for storing migration C# scripts.

For my case, there is only one website portal and one shared migration project (where all migration C# scripts locate); thus I just used those two projects.

format_quote

person Uday access_time 3 years ago

In Entity framework core migration script generator task
which path should be given for Main project path and DB context
as we have 6 portals and 15 function apps in one Repo

U Uday Kumar #1619 access_time 3 years ago more_vert

In Entity framework core migration script generator task
which path should be given for Main project path and DB context
as we have 6 portals and 15 function apps in one Repo

Raymond Raymond #1618 access_time 3 years ago more_vert

There are several things you can do:

1) Backup your database before migration so that you can restore if any failure.

2) You can use dotnet ef migrations script to generate roll back script too.

For example, the following command will generate scripts to roll back from a migration named LatestMigrationName to PreviousMigrationName.

dotnet ef migrations script LatestMigrationName PreviousMigrationName


format_quote

person Uday access_time 3 years ago

how can we rollback the Migration in case of failure

U Uday Kumar #1617 access_time 3 years ago more_vert

how can we rollback the Migration in case of failure

Please log in or register to comment.

account_circle Log in person_add Register

Log in with external accounts