Nguon: Microsoft Learn · .NET 8.0

Phần 4: Razor Pages với EF Core trong ASP.NET Core - Migrations

Nguồn: Part 4: Razor Pages - EF 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):

powershell
Drop-Database

Visual Studio Code

``dotnetcli dotnet tool install --global dotnet-ef ``

``dotnetcli dotnet ef database drop --force ``


Tạo initial migration

Visual Studio

Chạy các lệnh sau trong PMC:

powershell
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:

dotnetcli
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:

csharp
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:

csharp
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:

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

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:

Khắc phục sự cố

Nếu ứng dụng sử dụng SQL Server LocalDB và hiển thị exception sau:

text
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.

Tài nguyên bổ sung