Application data
.NET Core C# Entity Framework Core Fluent Assertion Powershell SQL

How to Apply Database Schema Changes using Entity Framework Core in .NET Core Applications

Welcome to today’s blog.

In this post I will show you show to make changes to SQL database schema using Entity Framework Core within your .NET Core application.

Before covering the procedures for changes to Entity Framework schemas, I will give an overview of how Entity Framework Core tracks and applies changes to a SQL database.

To understand how Entity Framework Core is installed and setup within a .NET Core application, you can refer to my previous post, where I introduced the use of Entity Framework Core within a .NET Core application.

How Entity Framework Core Tracks Database Changes

Each table in your schema is defined in the application data context class.

Below you see an example of a data context defining a data schema:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;
using BookLoan.Models;

namespace BookLoan.Data
{
    public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
    {
        public ApplicationDbContext (DbContextOptions<ApplicationDbContext> options)
            : base(options)
        {
        }

        protected override void OnModelCreating(ModelBuilder builder)
        {
            base.OnModelCreating(builder);

            builder.Entity<BookViewModel>().ToTable("Books");
                builder.Entity<ReviewViewModel>().ToTable("Reviews");
                builder.Entity<LoanViewModel>().ToTable("Loans");

                builder.Entity<ReviewViewModel>().
                    HasOne(p => p.Book).
                    WithMany(q => q.Reviews).
                    HasForeignKey(p => p.BookID).HasConstraintName("FK_Book_Review");

                builder.Entity<LoanViewModel>().
                    HasOne(p => p.Book).
                    WithMany(q => q.Loans).    
                    HasForeignKey(p => p.BookID).HasConstraintName("FK_Book_Loan");
        }

        public DbSet<BookViewModel> Books { get; set; }
        public DbSet<LoanViewModel> Loans { get; set; }
        public DbSet<ReviewViewModel> Reviews { get; set; }
    }
}

Any customizations to the schema (tables, keys, constraints) are defined within the overridden method:

OnModelCreating()

Each table has an associated Model definition, which defines a table schema. When the initial data context is migrated, Entity Framework applies the definition of each model to create tables within the SQL database.

An example of a model is shown below:

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Threading.Tasks;

namespace BookLoan.Models
{
    public class LoanViewModel
    {
        public int ID { get; set; }
        public string LoanedBy { get; set; }
        public DateTime DateLoaned { get; set; }
        public DateTime DateReturn { get; set; }
        public bool OnShelf { get; set; }
        public DateTime DateCreated { get; set; }
        public DateTime DateUpdated { get; set; }
        public int BookID { get; set; }
        public BookViewModel Book { get; set; }
    }
}

In the next section I will show how changes to the above model classes can be applied to generate migration classes, which can then be subsequently applied to migrate changes to the underlying SQL database.

Data Changes Tracked Through Migration Classes

When a change is made to one of our models, the equivalent schema change is generated as an Entity Framework Core migration class as shown under the \Data\Migrations\ folder:

An example of one of the migration classes is shown below:

using Microsoft.EntityFrameworkCore.Migrations;
using System;
using System.Collections.Generic;

namespace BookLoan.Data.Migrations
{
    public partial class add_bookloan_newfields_12_11_2019 : Migration
    {
        protected override void Up(MigrationBuilder migrationBuilder)
        {
            migrationBuilder.AddColumn<DateTime>(
                name: "DateDue",
                table: "Loans",
                nullable: false,
                defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified));
        }

        protected override void Down(MigrationBuilder migrationBuilder)
        {
            migrationBuilder.DropColumn(
                name: "DateDue",
                table: "Loans");
        }
    }
}

When a migration change is initially generated, the underlying database has not yet had the changes applied to it. The migration changes are staging the changes to our database. When the migrations are executed against the database, the changes to the schema will then be applied.

I will show how a basic schema change can be applied using Entity Framework Core:

Applying Schema Changes to Migration Classes

From an existing model, it is straightforward; we add an additional field DateDue to the model as shown:

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Threading.Tasks;

namespace BookLoan.Models
{
    public class LoanViewModel
    {
        public int ID { get; set; }
        public string LoanedBy { get; set; }
        public DateTime DateLoaned { get; set; }
        public DateTime DateDue { get; set; }
        public DateTime DateReturn { get; set; }
        public bool OnShelf { get; set; }
        public DateTime DateCreated { get; set; }
        public DateTime DateUpdated { get; set; }
        public int BookID { get; set; }

        public BookViewModel Book { get; set; }
    }
}

Save the change.

Rebuild. Open the package manager console (which is PowerShell):

Entered and ran the following command:

Add-Migration add_bookloan_newfields_12_11_2019

What the above add-migration Entity Framework Core command did was to add the migration class file as shown:

Also, the data context model snapshot class will have been updated with any new schema definitions:

namespace BookLoan.Data.Migrations
{
    [DbContext(typeof(ApplicationDbContext))]
    partial class ApplicationDbContextModelSnapshot : ModelSnapshot
    {
        protected override void BuildModel(
              ModelBuilder modelBuilder)
        {
#pragma warning disable 612, 618
            modelBuilder
                .HasAnnotation("ProductVersion", 
"2.0.3-rtm-10026")
.HasAnnotation( "SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
			..
			..
            modelBuilder.Entity
("BookLoan.Models.LoanViewModel", b =>
                {
                    b.Property<int>("ID")
                        .ValueGeneratedOnAdd();
			..
			..
                    b.Property<DateTime>("DateDue");
			..
                    b.HasKey("ID");

                    b.HasIndex("BookID");
                    b.ToTable("Loans");
                });
#pragma warning restore 612, 618
        }
    }
}

Rolling Back a Migration

Note: Before applying the update to the database, the above migration can be reversed by running the command:

Remove-Migration

After a migration has been removed, you can amend your model(s) and re-apply the migration.

Note: After a migration is applied to database, the remove-migration command cannot rollback the change! You can apply a rollback to a previous migration by using the command:

Update-Database LastMigration

Where LastMigration is your last successful migration.

What this will do is reverse your schema changes (and __EFMigrationHistory changes) applying the Down() method of each migration back to migration following the LastMigration migration.

Rolling Back a Migration

To apply the migration to the database, we ran the following command:

Update-database

What this does is to generate the equivalent SQL DDL and apply it to the target database.

The __EFMigrationsHistory is used by Entity Framework to keep track of schema changes to the database and determining if a migration has already been applied.

Also, a record showing the migration (if successful) is inserted:

Generation of a SQL Deployment Script

To generate a script that can be applied directly to the database offline or through a deployment pipeline, you can use the following command:

Script-Migration -Idempotent -Output [output file name]

The Idempotent switch will generate a script that can be run to bring any database schema up to date with the last migration. Entries in the script will be of the form:

IF NOT EXISTS(SELECT * FROM [__EFMigrationsHistory] WHERE [MigrationId] = N'20191112075920_add_bookloan_newfields_12_11_2019')
BEGIN
    ALTER TABLE [Loans] ADD [DateDue] datetime2 NOT NULL DEFAULT '0001-01-01T00:00:00.000';
END;
GO 

The above commands have shown you how to make changes to Entity Framework database schemas within .NET Core to generate C# migration classes, then apply the changes to these migration classes to the SQL database with migration updates using PowerShell commands.

That’s all for today’s post.

I hope you found it useful and informative.

Social media & sharing icons powered by UltimatelySocial