Migrations - EF Core with ASP.NET Core MVC tutorial (4 of 10)
By Tom Dykstra and Rick Anderson
The Contoso University sample web application demonstrates how to create ASP.NET Core MVC web applications using Entity Framework Core and Visual Studio. For information about the tutorial series, see the first tutorial in the series.
In this tutorial, you start using the EF Core migrations feature for managing data model changes. In later tutorials, you’ll add more migrations as you change the data model.
Introduction to migrations
When you develop a new application, your data model changes frequently, and each time the model changes, it gets out of sync with the database. You started these tutorials by configuring the Entity Framework to create the database if it doesn’t exist. Then each time you change the data model – add, remove, or change entity classes or change your DbContext class – you can delete the database and EF creates a new one that matches the model, and seeds it with test data.
This method of keeping the database in sync with the data model works well until you deploy the application to production. When the application is running in production it is usually storing data that you want to keep, and you don’t want to lose everything each time you make a change such as adding a new column. The EF Core Migrations feature solves this problem by enabling EF to update the database schema instead of creating a new database.
Entity Framework Core NuGet packages for migrations
To work with migrations, you can use the Package Manager Console (PMC) or the command-line interface (CLI). These tutorials show how to use CLI commands. Information about the PMC is at the end of this tutorial.
The EF tools for the command-line interface (CLI) are provided in Microsoft.EntityFrameworkCore.Tools.DotNet. To install this package, add it to the DotNetCliToolReference
collection in the .csproj file, as shown. Note: You have to install this package by editing the .csproj file; you can’t use the install-package
command or the package manager GUI. You can edit the .csproj file by right-clicking the project name in Solution Explorer and selecting Edit ContosoUniversity.csproj.
[!code-xmlMain]
1: <Project Sdk="Microsoft.NET.Sdk.Web">
2:
3: <PropertyGroup>
4: <TargetFramework>netcoreapp2.0</TargetFramework>
5: </PropertyGroup>
6:
7: <ItemGroup>
8: <PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.0" />
9: <PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="2.0.0" />
10: </ItemGroup>
11:
12: <ItemGroup>
13: <DotNetCliToolReference Include="Microsoft.EntityFrameworkCore.Tools.DotNet" Version="2.0.0" />
14: <DotNetCliToolReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Tools" Version="2.0.0" />
15: </ItemGroup>
16:
17: </Project>
Change the connection string
In the appsettings.json file, change the name of the database in the connection string to ContosoUniversity2 or some other name that you haven’t used on the computer you’re using.
[!code-jsonMain]
1: {
2: "ConnectionStrings": {
3: "DefaultConnection": "Server=(localdb)\\mssqllocaldb;Database=ContosoUniversity2;Trusted_Connection=True;MultipleActiveResultSets=true"
4: },
5: "Logging": {
6: "IncludeScopes": false,
7: "LogLevel": {
8: "Default": "Warning"
9: }
10: }
11: }
This change sets up the project so that the first migration will create a new database. This isn’t required for getting started with migrations, but you’ll see later why it’s a good idea.
[!NOTE] As an alternative to changing the database name, you can delete the database. Use SQL Server Object Explorer (SSOX) or the
database drop
CLI command:dotnet ef database drop
The following section explains how to run CLI commands.
Create an initial migration
Save your changes and build the project. Then open a command window and navigate to the project folder. Here’s a quick way to do that:
In Solution Explorer, right-click the project and choose Open in File Explorer from the context menu.
Enter “cmd” in the address bar and press Enter.
Enter the following command in the command window:
dotnet ef migrations add InitialCreate
You see output like the following in the command window:
info: Microsoft.AspNetCore.DataProtection.KeyManagement.XmlKeyManager[0]
User profile is available. Using 'C:\Users\username\AppData\Local\ASP.NET\DataProtection-Keys' as key repository and Windows DPAPI to encrypt keys at rest.
info: Microsoft.EntityFrameworkCore.Infrastructure[100403]
Entity Framework Core 2.0.0-rtm-26452 initialized 'SchoolContext' using provider 'Microsoft.EntityFrameworkCore.SqlServer' with options: None
Done. To undo this action, use 'ef migrations remove'
[!NOTE] If you see an error message No executable found matching command “dotnet-ef”, see this blog post for help troubleshooting.
If you see an error message “cannot access the file … ContosoUniversity.dll because it is being used by another process.”, find the IIS Express icon in the Windows System Tray, and right-click it, then click ContosoUniversity > Stop Site.
Examine the Up and Down methods
When you executed the migrations add
command, EF generated the code that will create the database from scratch. This code is in the Migrations folder, in the file named *<timestamp>_InitialCreate.cs*. The Up
method of the InitialCreate
class creates the database tables that correspond to the data model entity sets, and the Down
method deletes them, as shown in the following example.
[!code-csharpMain]
1: using System;
2: using System.Collections.Generic;
3: using Microsoft.EntityFrameworkCore.Migrations;
4: using Microsoft.EntityFrameworkCore.Metadata;
5:
6: namespace ContosoUniversity.Migrations
7: {
8: public partial class InitialCreate : Migration
9: {
10: protected override void Up(MigrationBuilder migrationBuilder)
11: {
12: migrationBuilder.CreateTable(
13: name: "Course",
14: columns: table => new
15: {
16: CourseID = table.Column<int>(nullable: false),
17: Credits = table.Column<int>(nullable: false),
18: Title = table.Column<string>(nullable: true)
19: },
20: constraints: table =>
21: {
22: table.PrimaryKey("PK_Course", x => x.CourseID);
23: });
24:
25: migrationBuilder.CreateTable(
26: name: "Student",
27: columns: table => new
28: {
29: ID = table.Column<int>(nullable: false)
30: .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn),
31: EnrollmentDate = table.Column<DateTime>(nullable: false),
32: FirstMidName = table.Column<string>(nullable: true),
33: LastName = table.Column<string>(nullable: true)
34: },
35: constraints: table =>
36: {
37: table.PrimaryKey("PK_Student", x => x.ID);
38: });
39:
40: migrationBuilder.CreateTable(
41: name: "Enrollment",
42: columns: table => new
43: {
44: EnrollmentID = table.Column<int>(nullable: false)
45: .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn),
46: CourseID = table.Column<int>(nullable: false),
47: Grade = table.Column<int>(nullable: true),
48: StudentID = table.Column<int>(nullable: false)
49: },
50: constraints: table =>
51: {
52: table.PrimaryKey("PK_Enrollment", x => x.EnrollmentID);
53: table.ForeignKey(
54: name: "FK_Enrollment_Course_CourseID",
55: column: x => x.CourseID,
56: principalTable: "Course",
57: principalColumn: "CourseID",
58: onDelete: ReferentialAction.Cascade);
59: table.ForeignKey(
60: name: "FK_Enrollment_Student_StudentID",
61: column: x => x.StudentID,
62: principalTable: "Student",
63: principalColumn: "ID",
64: onDelete: ReferentialAction.Cascade);
65: });
66:
67: migrationBuilder.CreateIndex(
68: name: "IX_Enrollment_CourseID",
69: table: "Enrollment",
70: column: "CourseID");
71:
72: migrationBuilder.CreateIndex(
73: name: "IX_Enrollment_StudentID",
74: table: "Enrollment",
75: column: "StudentID");
76: }
77:
78: protected override void Down(MigrationBuilder migrationBuilder)
79: {
80: migrationBuilder.DropTable(
81: name: "Enrollment");
82:
83: migrationBuilder.DropTable(
84: name: "Course");
85:
86: migrationBuilder.DropTable(
87: name: "Student");
88: }
89: }
90: }
91: #if Truncate
92: public partial class InitialCreate : Migration
93: {
94: protected override void Up(MigrationBuilder migrationBuilder)
95: {
96: migrationBuilder.CreateTable(
97: name: "Course",
98: columns: table => new
99: {
100: CourseID = table.Column<int>(nullable: false),
101: Credits = table.Column<int>(nullable: false),
102: Title = table.Column<string>(nullable: true)
103: },
104: constraints: table =>
105: {
106: table.PrimaryKey("PK_Course", x => x.CourseID);
107: });
108:
109: // Additional code not shown
110: }
111:
112: protected override void Down(MigrationBuilder migrationBuilder)
113: {
114: migrationBuilder.DropTable(
115: name: "Enrollment");
116: // Additional code not shown
117: }
118: }
119: #endif
Migrations calls the Up
method to implement the data model changes for a migration. When you enter a command to roll back the update, Migrations calls the Down
method.
This code is for the initial migration that was created when you entered the migrations add InitialCreate
command. The migration name parameter (“InitialCreate” in the example) is used for the file name and can be whatever you want. It’s best to choose a word or phrase that summarizes what is being done in the migration. For example, you might name a later migration “AddDepartmentTable”.
If you created the initial migration when the database already exists, the database creation code is generated but it doesn’t have to run because the database already matches the data model. When you deploy the app to another environment where the database doesn’t exist yet, this code will run to create your database, so it’s a good idea to test it first. That’s why you changed the name of the database in the connection string earlier – so that migrations can create a new one from scratch.
Examine the data model snapshot
Migrations also creates a snapshot of the current database schema in Migrations/SchoolContextModelSnapshot.cs. Here’s what that code looks like:
[!code-csharpMain]
1: #if Truncate
2: using System;
3: using Microsoft.EntityFrameworkCore;
4: using Microsoft.EntityFrameworkCore.Infrastructure;
5: using Microsoft.EntityFrameworkCore.Metadata;
6: using Microsoft.EntityFrameworkCore.Migrations;
7: using ContosoUniversity.Data;
8:
9: namespace ContosoUniversity.Migrations
10: {
11: #region snippet_Truncate
12: [DbContext(typeof(SchoolContext))]
13: partial class SchoolContextModelSnapshot : ModelSnapshot
14: {
15: protected override void BuildModel(ModelBuilder modelBuilder)
16: {
17: modelBuilder
18: .HasAnnotation("ProductVersion", "2.0.0-rtm-26452")
19: .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
20:
21: modelBuilder.Entity("ContosoUniversity.Models.Course", b =>
22: {
23: b.Property<int>("CourseID");
24:
25: b.Property<int>("Credits");
26:
27: b.Property<string>("Title");
28:
29: b.HasKey("CourseID");
30:
31: b.ToTable("Course");
32: });
33:
34: // Additional code for Enrollment and Student tables not shown
35:
36: modelBuilder.Entity("ContosoUniversity.Models.Enrollment", b =>
37: {
38: b.HasOne("ContosoUniversity.Models.Course", "Course")
39: .WithMany("Enrollments")
40: .HasForeignKey("CourseID")
41: .OnDelete(DeleteBehavior.Cascade);
42:
43: b.HasOne("ContosoUniversity.Models.Student", "Student")
44: .WithMany("Enrollments")
45: .HasForeignKey("StudentID")
46: .OnDelete(DeleteBehavior.Cascade);
47: });
48: }
49: }
50: #endregion
51: }
52: #endif
Because the current database schema is represented in code, EF Core doesn’t have to interact with the database to create migrations. When you add a migration, EF determines what changed by comparing the data model to the snapshot file. EF interacts with the database only when it has to update the database.
The snapshot file has to be kept in sync with the migrations that create it, so you can’t remove a migration just by deleting the file named *<timestamp>_<migrationname>.cs*. If you delete that file, the remaining migrations will be out of sync with the database snapshot file. To delete the last migration that you added, use the dotnet ef migrations remove command.
Apply the migration to the database
In the command window, enter the following command to create the database and tables in it.
dotnet ef database update
The output from the command is similar to the migrations add
command, except that you see logs for the SQL commands that set up the database. Most of the logs are omitted in the following sample output. If you prefer not to see this level of detail in log messages, you can change the log level in the appsettings.Development.json file. For more information, see (xref:)Introduction to logging.
info: Microsoft.AspNetCore.DataProtection.KeyManagement.XmlKeyManager[0]
User profile is available. Using 'C:\Users\username\AppData\Local\ASP.NET\DataProtection-Keys' as key repository and Windows DPAPI to encrypt keys at rest.
info: Microsoft.EntityFrameworkCore.Infrastructure[100403]
Entity Framework Core 2.0.0-rtm-26452 initialized 'SchoolContext' using provider 'Microsoft.EntityFrameworkCore.SqlServer' with options: None
info: Microsoft.EntityFrameworkCore.Database.Command[200101]
Executed DbCommand (467ms) [Parameters=[], CommandType='Text', CommandTimeout='60']
CREATE DATABASE [ContosoUniversity2];
info: Microsoft.EntityFrameworkCore.Database.Command[200101]
Executed DbCommand (20ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
CREATE TABLE [__EFMigrationsHistory] (
[MigrationId] nvarchar(150) NOT NULL,
[ProductVersion] nvarchar(32) NOT NULL,
CONSTRAINT [PK___EFMigrationsHistory] PRIMARY KEY ([MigrationId])
);
<logs omitted for brevity>
info: Microsoft.EntityFrameworkCore.Database.Command[200101]
Executed DbCommand (3ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
INSERT INTO [__EFMigrationsHistory] ([MigrationId], [ProductVersion])
VALUES (N'20170816151242_InitialCreate', N'2.0.0-rtm-26452');
Done.
Use SQL Server Object Explorer to inspect the database as you did in the first tutorial. You’ll notice the addition of an __EFMigrationsHistory table that keeps track of which migrations have been applied to the database. View the data in that table and you’ll see one row for the first migration. (The last log in the preceding CLI output example shows the INSERT statement that creates this row.)
Run the application to verify that everything still works the same as before.
## Command-line interface (CLI) vs. Package Manager Console (PMC)
The EF tooling for managing migrations is available from .NET Core CLI commands or from PowerShell cmdlets in the Visual Studio Package Manager Console (PMC) window. This tutorial shows how to use the CLI, but you can use the PMC if you prefer.
The EF commands for the PMC commands are in the Microsoft.EntityFrameworkCore.Tools package. This package is already included in the (xref:)Microsoft.AspNetCore.All metapackage, so you don’t have to install it.
Important: This is not the same package as the one you install for the CLI by editing the .csproj file. The name of this one ends in Tools
, unlike the CLI package name which ends in Tools.DotNet
.
For more information about the CLI commands, see .NET Core CLI.
For more information about the PMC commands, see Package Manager Console (Visual Studio).
Summary
In this tutorial, you’ve seen how to create and apply your first migration. In the next tutorial, you’ll begin looking at more advanced topics by expanding the data model. Along the way you’ll create and apply additional migrations.
|