Share via


第 4 部分:ASP.NET Core 中包含 EF Core 移轉的 Razor 頁面

作者:Tom DykstraJon P SmithRick Anderson

Contoso 大學 Web 應用程式將示範如何使用 EF Core 和 Visual Studio 來建立 Razor Pages Web 應用程式。 如需教學課程系列的資訊,請參閱第一個教學課程

如果您遇到無法解決的問題,請下載已完成的應用程式,並遵循本教學課程以將程式碼與您所建立的內容進行比較。

本教學課程介紹用於管理資料模型變更的 EF Core 移轉功能。

開發新的應用程式時,資料模型經常變更。 每次模型變更時,模型就與資料庫不同步。 本教學課程系列從設定 Entity Framework 來建立不存在的資料庫開始。 每次資料模型變更時,都必須卸除資料庫。 下次執行應用程式時,對 EnsureCreated 的呼叫會重新建立資料庫,以符合新的資料模型。 然後,DbInitializer 類別會執行以植入新的資料庫。

在需要將應用程式部署到生產環境之前,這種讓資料庫與資料模型保持同步的方法效果不錯。 當應用程式在生產環境中執行時,它通常會儲存需要維護的資料。 應用程式不能每次進行變更 (例如新增資料行) 時,都從測試資料庫開始。 EF Core 移轉功能可解決這個問題,方法是啟用 EF Core 以更新資料庫結構描述,來代替建立新的資料庫。

移轉會更新結構描述並保留現有的資料,而不是在資料模型變更時卸除並重新建立資料庫。

注意

SQLite 限制

本教學課程盡可能使用 Entity Framework Core「移轉」功能。 移轉可更新資料庫結構描述,以符合資料模型中的變更。 不過,移轉只會執行資料庫引擎支援的變更種類,而 SQLite 的結構描述變更功能會受到限制。 例如,其支援新增資料行,但不支援移除資料行。 如果您建立移轉來移除資料行,ef migrations add 命令會成功;但 ef database update 命令會失敗。

SQLite 限制的因應措施是手動撰寫移轉程式碼,以在資料表有所變更時執行資料表重建。 程式碼會進入 UpDown 方法以進行移轉,且其中會涉及:

  • 建立新的資料表。
  • 將資料從舊的資料表複製到新的資料表。
  • 卸除舊的資料表。
  • 重新命名新資料表。

撰寫此型別的資料庫特定程式碼不在本教學課程範圍內。 反之,當嘗試套用移轉失敗時,本教學課程會卸除並重新建立資料庫。 如需詳細資訊,請參閱以下資源:

卸除資料庫

使用 SQL Server 物件總管 (SSOX) 刪除資料庫,或在套件管理員主控台 (PMC) 中執行下列命令:

Drop-Database

建立初始移轉

在 PMC 中執行下列命令:

Add-Migration InitialCreate
Update-Database

移除 EnsureCreated

本教學課程系列使用 EnsureCreated 來開始。 EnsureCreated 不會建立移轉記錄資料表,因此無法用於移轉。 其設計目的是用來測試或快速原型化經常卸除並重新建立資料庫的位置。

從這裡開始,教學課程會使用移轉。

Program.cs 中,刪除下列這一行:

context.Database.EnsureCreated();

執行應用程式,並確認資料庫已植入。

Up 和 Down 方法

EF Coremigrations 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"。

移轉記錄資料表

  • 使用 SSOX 或 SQLite 工具來檢查資料庫。
  • 請注意已新增 __EFMigrationsHistory 資料表。 __EFMigrationsHistory 資料表會追蹤哪些移轉已套用至資料庫。
  • 檢視 __EFMigrationsHistory 資料表中的資料。 第一次移轉時會顯示一個資料列。

資料模型快照集

移轉會在 Migrations/SchoolContextModelSnapshot.cs 中建立目前資料模型的快照集。 當新增移轉時,EF 會比較目前資料模型與快照集檔案,以判斷變更的內容。

由於快照集檔案會追蹤資料模型的狀態,您無法藉由刪除 <timestamp>_<migrationname>.cs 檔案來刪除移轉。 若要退出最新的移轉,請使用 migrations remove 命令。 migrations remove 會刪除移轉,並確保正確地重設快照集。 如需詳細資訊,請參閱 dotnet ef 移轉移除

請參閱重設所有移轉,以移除所有移轉。

在生產環境中套用移轉

建議在應用程式啟動時,生產環境應用程式不應該呼叫 Database.Migrate。 不應該從部署至伺服器陣列的應用程式呼叫 Migrate。 如果應用程式相應放大至多個伺服器執行個體,則很難確保不從多部伺服器進行資料庫結構描述更新,或與讀取/寫入存取發生衝突。

資料庫移轉應該在部署中以受控制的方式完成。 生產環境資料庫移轉方法包括:

  • 使用移轉來建立 SQL 指令碼,並在部署中使用 SQL 指令碼。
  • 從受控制的環境中執行 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

其他資源

下一步

下一個教學課程會建立資料模型,並新增實體屬性和新實體。

在本教學課程中,會使用 EF Core 移轉功能來管理資料模型變更。

若您遭遇到無法解決的問題,請下載完整應用程式

開發新的應用程式時,資料模型經常變更。 每次模型變更時,模型就與資料庫不同步。 本教學課程從設定 Entity Framework 來建立不存在的資料庫開始。 每次資料模型變更時:

  • 會卸除資料庫。
  • EF 會建立一個符合模型的新資料庫。
  • 應用程式會將測試資料植入資料庫。

在需要將應用程式部署到生產環境之前,這種讓資料庫與資料模型保持同步的方法效果不錯。 當應用程式在生產環境中執行時,它通常會儲存需要維護的資料。 應用程式不能每次進行變更 (例如新增資料行) 時,都從測試資料庫開始。 EF Core 移轉功能可解決這個問題,方法是啟用 EF Core 以更新資料庫結構描述,來代替建立新的資料庫。

移轉會更新結構描述,並保留現有的資料,而不是在資料模型變更時,卸除並重新建立資料庫。

卸除資料庫

使用 [SQL Server 物件總管] (SSOX) 或 database drop 命令:

在 [套件管理員主控台] (PMC) 中,執行下列命令:

Drop-Database

從 PMC 執行 Get-Help about_EntityFrameworkCore 以取得說明資訊。

建立初始移轉並更新資料庫

建置專案並建立第一個移轉。

Add-Migration InitialCreate
Update-Database

檢查 Up 和 Down 方法

EF Coremigrations add 命令已產生用來建立資料庫的程式碼。 此移轉程式碼位於 Migrations\<timestamp>_InitialCreate.cs 檔案中。 InitialCreate 類別的 Up 方法會建立對應至資料模型實體集的資料庫資料表。 Down 方法則會刪除它們,如下列範例所示:

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(
    protected override void Down(MigrationBuilder migrationBuilder)
    {
        migrationBuilder.DropTable(
            name: "Enrollment");

        migrationBuilder.DropTable(
            name: "Course");

        migrationBuilder.DropTable(
            name: "Student");
    }
}

Migrations 會呼叫 Up 方法,以實作移轉所需的資料模型變更。 當您輸入命令以復原更新時,移轉會呼叫 Down 方法。

上述程式碼適用於初始移轉。 該程式碼是在執行 migrations add InitialCreate 命令時建立。 移轉名稱參數 (在範例中為 "InitialCreate") 用於檔案名稱。 移轉名稱可以是任何有效的檔案名稱。 建議您選擇某個單字或片語,以摘要說明移轉中所要完成的作業。 例如,新增了部門資料表的移轉可能稱為 "AddDepartmentTable"。

如果已建立初始移轉並結束資料庫,則:

  • 會產生資料庫建立程式碼。
  • 不需要執行資料庫建立程式碼,因為資料庫已符合資料模型。 如果執行了資料庫建立程式碼,它不會進行任何變更,因為資料庫已符合資料模型。

當應用程式部署到新的環境時,必須執行資料庫建立程式碼來建立資料庫。

先前資料庫已卸除,並不存在,所以移轉會建立新資料庫。

資料模型快照集

移轉會在 Migrations/SchoolContextModelSnapshot.cs 中建立目前資料庫結構描述的快照集。 當您新增移轉時,EF 會比較資料模型與快照集檔案,以判斷變更的內容。

若要刪除移轉,請使用下列命令:

移除移轉

remove migrations 命令會刪除移轉,並確保正確地重設快照集。

移除 EnsureCreated 並測試應用程式

早期開發會使用 EnsureCreated。 在本教學課程中,則使用移轉。 EnsureCreated 具有下列限制:

  • 略過移轉,並建立資料庫和結構描述。
  • 不會建立移轉資料表。
  • 「無法」與移轉搭配使用。
  • 設計用來測試或快速原型化經常卸除並重新建立資料庫的位置。

移除 EnsureCreated

context.Database.EnsureCreated();

執行應用程式,並確認植入資料庫。

檢查資料庫

使用 SQL Server 物件總管來檢查資料庫。 請注意已新增 __EFMigrationsHistory 資料表。 __EFMigrationsHistory 資料表會追蹤哪些移轉經套用至資料庫。 檢視 __EFMigrationsHistory 資料表中的資料,它會顯示第一個移轉的某個資料列。 上述 CLI 輸出範例中的最後一則記錄會顯示建立此資料列的 INSERT 陳述式。

執行應用程式,並確認一切運作正常。

在生產環境中套用移轉

建議在應用程式啟動時,生產環境應用程式應該呼叫 Database.MigrateMigrate 不應該從伺服器陣列中的應用程式進行呼叫。 例如,如果應用程式已使用向外延展 (執行應用程式的多個執行個體) 進行雲端部署。

資料庫移轉應該在部署中以受控制的方式完成。 生產環境資料庫移轉方法包括:

  • 使用移轉來建立 SQL 指令碼,並在部署中使用 SQL 指令碼。
  • 從受控制的環境中執行 dotnet ef database update

EF Core 使用 __MigrationsHistory 資料表來查看是否有任何需要執行的移轉。 如果資料庫為最新狀態,則不會執行移轉。

疑難排解

下載完整應用程式

應用程式會產生下列例外狀況:

SqlException: Cannot open database "ContosoUniversity" requested by the login.
The login failed.
Login failed for user 'user name'.

解決方案:執行 dotnet ef database update

其他資源