作者:Tom Dykstra、Jon P Smith 和 Rick Anderson
Contoso University Web 应用演示了如何使用 EF Core 和 Visual Studio 创建 Razor 页面 Web 应用。 若要了解系列教程,请参阅第一个教程。
如果遇到无法解决的问题,请下载已完成的应用,然后对比该代码与按教程所创建的代码。
本教程介绍管理数据模型更改的 EF Core 迁移功能。
开发新应用时,数据模型会频繁更改。 每当模型发生更改时,都无法与数据库进行同步。 本教程从配置实体框架以创建数据库(如果不存在)开始。 数据模型每次发生更改时,必须删除该数据库。 下次应用运行时,对 EnsureCreated
的调用将重新创建数据库以匹配新的数据模型。 然后 DbInitializer
类将运行以设定新数据库的种子。
这种使 DB 与数据模型保持同步的方法适用于多种情况,但将应用部署到生产环境的情况除外。 当应用在生产环境中运行时,应用通常会存储需要保留的数据。 每当发生更改(例如添加新列)时,应用都无法在具有测试数据库的环境下启动。 EF Core 迁移功能通过启用 EF Core 更新数据库架构而不是创建新数据库来解决此问题。
数据模型更改时,迁移不会删除并重新创建数据库,而是更新架构并保留现有数据。
备注
SQLite 限制
本教程使用 Entity Framework Core 迁移功能(若可行) 。 迁移会更新数据库架构,使其与数据模型中的更改相匹配。 但迁移只执行数据库引擎支持的更改类型,而 SQLite 的架构更改功能受限。 例如,支持添加列,但不支持删除列。 如果已创建迁移以删除列,则 ef migrations add
命令将成功,但 ef database update
命令会失败。
要绕开 SQLite 限制,可手动写入迁移代码,在表内容更改时重新生成表。 代码将在 Up
和 Down
方法中用于迁移,并且将涉及以下内容:
本教程不涉及编写此类型的特定于数据库的代码。 相反,每当尝试应用迁移失败时,本教程将删除并重新创建数据库。 有关更多信息,请参见以下资源:
使用 SQL Server 对象资源管理器 (SSOX) 删除数据库或在包管理器控制台 (PMC) 中运行以下命令 :
Drop-Database
在命令提示符下运行以下命令以安装 EF CLI:
dotnet tool install --global dotnet-ef
在命令提示符下,导航到项目文件夹。 项目文件夹包含 ContosoUniversity.csproj 文件 。
删除 CU.db 文件,或运行以下命令 :
dotnet ef database drop --force
在 PMC 中运行以下命令:
Add-Migration InitialCreate Update-Database
确保命令提示符位于项目文件夹中,并运行以下命令:
dotnet ef migrations add InitialCreate dotnet ef database update
EF Core migrations add
命令已生成用于创建数据库的代码。 此迁移代码位于 Migrations<timestamp>_InitialCreate.cs 文件中 。 InitialCreate
类的 Up
方法创建与数据模型实体集对应的数据库表。 Down
方法删除这些表,如下例所示:
using System; using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Migrations; namespace ContosoUniversity.Migrations { public partial class InitialCreate : Migration { protected override void Up(MigrationBuilder migrationBuilder) { migrationBuilder.CreateTable( name: "Course", columns: table => new { CourseID = table.Column<int>(nullable: false), Title = table.Column<string>(nullable: true), Credits = table.Column<int>(nullable: false) }, constraints: table => { table.PrimaryKey("PK_Course", x => x.CourseID); }); migrationBuilder.CreateTable( name: "Student", columns: table => new { ID = table.Column<int>(nullable: false) .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn), LastName = table.Column<string>(nullable: true), FirstMidName = table.Column<string>(nullable: true), EnrollmentDate = table.Column<DateTime>(nullable: false) }, constraints: table => { table.PrimaryKey("PK_Student", x => x.ID); }); migrationBuilder.CreateTable( name: "Enrollment", columns: table => new { EnrollmentID = table.Column<int>(nullable: false) .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn), CourseID = table.Column<int>(nullable: false), StudentID = table.Column<int>(nullable: false), Grade = table.Column<int>(nullable: true) }, constraints: table => { table.PrimaryKey("PK_Enrollment", x => x.EnrollmentID); table.ForeignKey( name: "FK_Enrollment_Course_CourseID", column: x => x.CourseID, principalTable: "Course", principalColumn: "CourseID", onDelete: ReferentialAction.Cascade); table.ForeignKey( name: "FK_Enrollment_Student_StudentID", column: x => x.StudentID, principalTable: "Student", principalColumn: "ID", onDelete: ReferentialAction.Cascade); }); migrationBuilder.CreateIndex( name: "IX_Enrollment_CourseID", table: "Enrollment", column: "CourseID"); migrationBuilder.CreateIndex( name: "IX_Enrollment_StudentID", table: "Enrollment", column: "StudentID"); } protected override void Down(MigrationBuilder migrationBuilder) { migrationBuilder.DropTable( name: "Enrollment"); migrationBuilder.DropTable( name: "Course"); migrationBuilder.DropTable( name: "Student"); } } }
前面的代码适用于初始迁移。 代码:
migrations add InitialCreate
命令生成。database update
命令执行。迁移名称参数(本示例中为“InitialCreate”)用于指定文件名。 迁移名称可以是任何有效的文件名。 最好选择能概括迁移中所执行操作的字词或短语。 例如,添加了系表的迁移可称为“AddDepartmentTable”。
__EFMigrationsHistory
表。 __EFMigrationsHistory
表跟踪已应用到数据库的迁移。__EFMigrationsHistory
表中的数据。 它显示第一次迁移的行。迁移会在 Migrations/SchoolContextModelSnapshot.cs 中创建当前数据模型的快照 。 添加迁移时,EF 会通过将当前数据模型与快照文件进行对比来确定已更改的内容。
由于快照文件跟踪数据模型的状态,因此不能通过删除 <timestamp>_<migrationname>.cs
文件来删除迁移。 要返回最近的迁移,必须使用 migrations remove
命令。 该命令删除迁移并确保正确重置快照。 有关详细信息,请参阅 dotnet ef migrations remove。
本系列教程从使用 EnsureCreated
开始。 EnsureCreated
不创建迁移历史记录表,因此不能与迁移一起使用。 它专门用于在频繁删除并重新创建 DB 的情况下进行测试或快速制作原型。
从这个角度来看,教程将使用迁移。
在 Data/DBInitializer.cs 中,注释掉以下行 :
context.Database.EnsureCreated();
运行应用并验证数据库是否已设定种子。
不建议生产应用在应用程序启动时调用 Database.Migrate 。 Migrate
不应从部署到服务器场的应用中调用。 如果应用横向扩展到多个服务器实例,则很难确保多个服务器不会发生数据库架构更新,或者这些更新不会与读/写访问冲突。
应在部署过程中以受控的方式执行数据库迁移。 生产数据库迁移方法包括:
dotnet ef database update
。如果应用使用 SQL Server LocalDB 并显示以下异常:
SqlException: Cannot open database "ContosoUniversity" requested by the login. The login failed. Login failed for user 'user name'.
解决方案可能是在命令提示符下运行 dotnet ef database update
。
下一个教程将生成数据模型,并添加实体属性和新实体。