Phần 4: Razor Pages với EF Core trong ASP.NET Core - Migrations
Bởi Tom Dykstra, Jon P Smith, và Rick Anderson
Ứng dụng web Contoso University minh họa cách tạo ứng dụng web Razor Pages sử dụng EF Core và Visual Studio. Để biết thêm thông tin về series hướng dẫn này, xem hướng dẫn đầu tiên.
Nếu gặp vấn đề không giải quyết được, hãy tải ứng dụng hoàn chỉnh và so sánh code đó với những gì bạn đã tạo khi làm theo hướng dẫn.
Hướng dẫn này giới thiệu tính năng EF Core migrations (di chuyển) để quản lý thay đổi data model.
Khi một ứng dụng mới được phát triển, data model (mô hình dữ liệu) thường xuyên thay đổi. Mỗi khi model thay đổi, model mất đồng bộ với database. Series hướng dẫn này bắt đầu bằng cách cấu hình Entity Framework để tạo database nếu nó không tồn tại. Mỗi khi data model thay đổi, database cần được drop (xóa). Lần chạy ứng dụng tiếp theo, lệnh gọi EnsureCreated sẽ tạo lại database để khớp với data model mới. Lớp DbInitializer sau đó chạy để seed (nạp dữ liệu ban đầu) database mới.
Cách tiếp cận này để giữ database đồng bộ với data model hoạt động tốt cho đến khi ứng dụng cần được triển khai lên môi trường production. Khi ứng dụng đang chạy trong production, nó thường lưu trữ dữ liệu cần được duy trì. Ứng dụng không thể bắt đầu với một test DB mỗi khi có thay đổi (chẳng hạn như thêm một cột mới). Tính năng EF Core Migrations giải quyết vấn đề này bằng cách cho phép EF Core cập nhật schema (lược đồ) DB thay vì tạo database mới.
Thay vì drop và recreate database khi data model thay đổi, migrations cập nhật schema và giữ lại dữ liệu hiện có.
Lưu ý: Giới hạn của SQLite
Hướng dẫn này sử dụng tính năng migrations của Entity Framework Core nơi có thể. Migrations cập nhật schema database để phù hợp với thay đổi trong data model. Tuy nhiên, migrations chỉ thực hiện các loại thay đổi mà database engine hỗ trợ, và khả năng thay đổi schema của SQLite bị hạn chế. Ví dụ, thêm cột được hỗ trợ, nhưng xóa cột thì không. Nếu migration được tạo để xóa một cột, lệnh ef migrations add thành công nhưng lệnh ef database update thất bại.
Cách giải quyết cho giới hạn SQLite là viết thủ công code migration để thực hiện table rebuild khi có gì đó thay đổi trong bảng.
Drop database
Visual Studio
Sử dụng SQL Server Object Explorer (SSOX) để xóa database, hoặc chạy lệnh sau trong Package Manager Console (PMC):
Drop-Database
Visual Studio Code
- Chạy lệnh sau tại command prompt để cài đặt EF CLI:
``dotnetcli dotnet tool install --global dotnet-ef ``
- Trong command prompt, điều hướng đến thư mục project. Thư mục project chứa file
ContosoUniversity.csproj. - Xóa file
CU.db, hoặc chạy lệnh sau:
``dotnetcli dotnet ef database drop --force ``
Tạo initial migration
Visual Studio
Chạy các lệnh sau trong PMC:
Add-Migration InitialCreate Update-Database
Visual Studio Code
Đảm bảo command prompt đang ở thư mục project, và chạy các lệnh sau:
dotnet ef migrations add InitialCreate dotnet ef database update
Xóa EnsureCreated
Series hướng dẫn này bắt đầu bằng cách sử dụng EnsureCreated. EnsureCreated không tạo bảng lịch sử migrations và vì vậy không thể sử dụng với migrations. Nó được thiết kế để kiểm thử hoặc rapid prototyping (tạo mẫu nhanh) nơi database thường xuyên bị drop và recreate.
Từ đây trở đi, các hướng dẫn sẽ sử dụng migrations.
Trong Program.cs, xóa dòng sau:
context.Database.EnsureCreated();
Chạy ứng dụng và xác minh rằng database được seed.
Các phương thức Up và Down
Lệnh migrations add của EF Core tạo code để tạo database. Code migrations này nằm trong file Migrations\<timestamp>_InitialCreate.cs. Phương thức Up của lớp InitialCreate tạo các bảng database tương ứng với các entity set của data model. Phương thức Down xóa chúng, như được hiển thị trong ví dụ sau:
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");
}
}
}Code trên là cho initial migration. Code:
- Được tạo bởi lệnh
migrations add InitialCreate. - Được thực thi bởi lệnh
database update. - Tạo database cho data model được chỉ định bởi lớp database context.
Tham số tên migration (InitialCreate trong ví dụ) được sử dụng cho tên file. Tên migration có thể là bất kỳ tên file hợp lệ nào. Tốt nhất nên chọn một từ hoặc cụm từ tóm tắt những gì đang được thực hiện trong migration. Ví dụ, migration thêm bảng department có thể được gọi là "AddDepartmentTable."
Bảng lịch sử migrations
- Sử dụng SSOX hoặc công cụ SQLite để kiểm tra database.
- Chú ý việc bổ sung bảng
__EFMigrationsHistory. Bảng__EFMigrationsHistorytheo dõi các migrations đã được áp dụng vào database. - Xem dữ liệu trong bảng
__EFMigrationsHistory. Nó hiển thị một hàng cho migration đầu tiên.
Snapshot data model
Migrations tạo một snapshot (ảnh chụp) của data model hiện tại trong Migrations/SchoolContextModelSnapshot.cs. Khi một migration được thêm, EF xác định những gì đã thay đổi bằng cách so sánh data model hiện tại với file snapshot.
Vì file snapshot theo dõi trạng thái của data model, migration không thể bị xóa bằng cách xóa file <timestamp>_<migrationname>.cs. Để hủy migration gần nhất, sử dụng lệnh migrations remove. Lệnh migrations remove xóa migration và đảm bảo snapshot được reset đúng cách.
Xem Resetting all migrations để xóa tất cả migrations.
Áp dụng migrations trong môi trường production
Chúng tôi khuyến nghị các ứng dụng production không gọi Database.Migrate khi khởi động ứng dụng. Migrate không nên được gọi từ một ứng dụng được triển khai lên server farm (cụm máy chủ). Nếu ứng dụng được scale out (mở rộng) sang nhiều server instance, thì khó đảm bảo rằng việc cập nhật database schema không xảy ra từ nhiều server hoặc xung đột với quyền truy cập đọc/ghi.
Migration database nên được thực hiện như một phần của quá trình deployment (triển khai), và theo cách có kiểm soát. Các cách tiếp cận migration database trong production bao gồm:
- Sử dụng migrations để tạo SQL scripts và sử dụng SQL scripts trong deployment.
- Chạy
dotnet ef database updatetừ môi trường được kiểm soát.
Khắc phục sự cố
Nếu ứng dụng sử dụng SQL Server LocalDB và hiển thị exception sau:
SqlException: Cannot open database "ContosoUniversity" requested by the login. The login failed. Login failed for user 'user name'.
Giải pháp có thể là chạy dotnet ef database update tại command prompt.