パート 4、ASP.NET Core の Razor Pages と EF Core の移行

作成者: Tom DykstraJon P SmithRick Anderson

Contoso 大学 Web アプリでは、EF Core と Visual Studio を使用して Razor Pages Web アプリを作成する方法を示します。 チュートリアル シリーズについては、最初のチュートリアルを参照してください。

解決できない問題が発生した場合は、完成したアプリをダウンロードし、チュートリアルに従って作成した内容とコードを比較します。

このチュートリアルでは、データ モデルの変更を管理するための EF Core の移行機能を紹介します。

新しいアプリを開発するときには、頻繁にデータ モデルを変更します。 モデルを変更するたびに、モデルはデータベースと同期されなくなります。 このチュートリアル シリーズでは、最初にデータベースが存在しない場合に、データベースを作成するように Entity Framework を構成します。 データ モデルが変更されるたびに、データベースを削除する必要があります。 次にアプリが実行されると、EnsureCreated への呼び出しによって、新しいデータ モデルに一致するデータベースが再作成されます。 その後、DbInitializer クラスが実行され、新しいデータベースがシードされます。

この DB とデータ モデルを同期させるアプローチは、実稼働環境にアプリを展開する必要があるまでは適切に機能します。 アプリが実稼働環境で実行されているときには、通常は維持する必要があるデータをアプリが格納します。 変更 (新しい列の追加など) が加えられるたびにテスト データベースを使用してアプリを開始することはできません。 EF Core の移行機能は、新しいデータベースを作成する代わりに EF Core で DB スキーマを更新できるようにすることでこの問題を解決します。

データ モデルが変更されたときにデータベースを削除して再作成する代わりに、移行によってスキーマを更新し、既存のデータを維持します。

Note

SQLite の制限事項

このチュートリアルでは、可能な場合は Entity Framework Core の移行機能を使います。 移行では、データ モデルの変更に合わせてデータベース スキーマが更新されます。 しかし、移行ではデータベース エンジンによってサポートされる変更の種類のみが行われ、SQLite のスキーマ変更機能は制限されます。 たとえば、列の追加はサポートされていますが、列の削除はサポートされていません。 列を削除するために移行を作成した場合、ef migrations add コマンドは成功しますが、ef database update コマンドは失敗します。

SQLite の制限に対する回避策としては、テーブル内の何かが変更されたときにテーブルの再構築を実行する移行コードを、手動で作成します。 このコードは移行のために Up メソッドと Down メソッドに含まれ、次のことを行います。

  • 新しいテーブルの作成。
  • 古いテーブルから新しいテーブルへのデータのコピー。
  • 古いテーブルの削除。
  • 新しいテーブルの名前変更。

この種類のデータベース固有のコードを記述することは、このチュートリアルの範囲外です。 代わりに、このチュートリアルでは、移行を適用しようとして失敗した場合に、データベースを削除して再作成します。 詳細については、次のリソースを参照してください。

データベースを削除する

SQL Server オブジェクトエクスプローラー (SSOX) を使用してデータベースを削除するか、パッケージ マネージャー コンソール (PMC) で次のコマンドを実行します。

Drop-Database

初期移行を作成する

PMC で次のコマンドを実行します。

Add-Migration InitialCreate
Update-Database

Remove 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 コマンドによって実行されます。
  • データベース コンテキスト クラスによって指定されたデータ モデル用のデータベースを作成します。

移行の name パラメーター (この例では InitialCreate) は、ファイル名に使用されます。 移行名には、任意の有効なファイル名を指定できます。 移行で行われている処理をまとめた単語または語句を選択することをお勧めします。 たとえば、department テーブルを追加する移行に "AddDepartmentTable" という名前を付けることができます。

移行履歴テーブル

  • SSOX または SQLite ツールを使用してデータベースを検査します。
  • __EFMigrationsHistory テーブルが追加されていることに注意してください。 __EFMigrationsHistory テーブルは、どの移行がデータベースに適用されたかを追跡します。
  • __EFMigrationsHistory テーブル内のデータを表示します。 最初の移行に対する 1 行が表示されます。

データ モデルのスナップショット

移行によって、現在のデータ モデルのスナップショットMigrations/SchoolContextModelSnapshot.cs 内に作成されます。 移行を追加するときに、EF では、スナップショット ファイルと現在のデータ モデルを比較することによって変更内容を判断します。

スナップショット ファイルではデータ モデルの状態が追跡されるため、<timestamp>_<migrationname>.cs ファイルを削除することによって移行を削除することはできません。 最新の移行を取り消すには、migrations remove コマンドを使用します。 migrations remove によって移行が削除され、スナップショットが正しくリセットされたことが確認されます。 詳細については、「dotnet ef migrations remove」を参照してください。

すべての移行を削除するには、すべての移行のリセットに関する記事を参照してください。

運用環境で移行を適用する

実稼働アプリでは、アプリケーションの起動時に 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 がモデルと一致する新しいデータベースを作成します。
  • アプリがテスト データを DB に格納します。

この DB とデータ モデルを同期させるアプローチは、実稼働環境にアプリを展開する必要があるまでは適切に機能します。 アプリが実稼働環境で実行されているときには、通常は維持する必要があるデータをアプリが格納します。 変更 (新しい列の追加など) が加えられるたびにテスト データベースを使用してアプリを開始することはできません。 EF Core の移行機能は、新しい DB を作成する代わりに EF Core で DB スキーマを更新できるようにすることでこの問題を解決します。

データ モデルが変更されたときにデータベースを削除して再作成する代わりに、移行によってスキーマを更新し、既存のデータを維持します。

データベースを削除する

SQL Server オブジェクト エクスプローラー (SSOX) または database drop コマンドを使用します。

パッケージ マネージャー コンソール (PMC) で、次のコマンドを実行します。

Drop-Database

PMC から Get-Help about_EntityFrameworkCore を実行してヘルプ情報を入手します。

初期移行を作成し、DB を更新する

プロジェクトをビルドし、最初の移行を作成します。

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");
    }
}

移行は、Up メソッドを呼び出して、移行のためのデータ モデルの変更を実装します。 更新をロールバックするためのコマンドを入力すると、移行が Down メソッドを呼び出します。

上記のコードは、初期の移行のためのコードです。 そのコードは、migrations add InitialCreate コマンドが実行されたときに作成されます。 移行の name パラメーター (この例では "InitialCreate") は、ファイル名に使用されます。 移行名には、任意の有効なファイル名を指定できます。 移行で行われている処理をまとめた単語または語句を選択することをお勧めします。 たとえば、department テーブルを追加する移行に "AddDepartmentTable" という名前を付けることができます。

最初の移行が作成され、データベースが存在している場合:

  • データベース作成コードが生成されます。
  • データベースは既にデータ モデルと一致しているため、データベース作成コードを実行する必要はありません。 データベース作成コードが実行された場合、データベースは既にデータ モデルと一致しているため、何も変更しません。

新しい環境にアプリが展開されるときには、データベースを作成するためのデータベース作成コードを実行する必要があります。

以前は DB が削除されて存在しないため、移行によって新しい DB が作成されていました。

データ モデルのスナップショット

移行は、現在のデータベース スキーマの "スナップショット" を Migrations/SchoolContextModelSnapshot.cs 内に作成します。 移行を追加するときに、EF は、スナップショット ファイルとデータ モデルを比較することによって変更内容を判断します。

移行を削除するには、次のコマンドを使用します。

Remove-Migration

remove migrations コマンドによって移行が削除され、スナップショットが正しくリセットされたことが確認されます。

EnsureCreated を削除し、アプリをテストする

初期の開発では、EnsureCreated が使用されました。 このチュートリアルでは、移行を使用します。 EnsureCreated には次の制限が適用されます。

  • 移行をバイパスし、データベースとスキーマを作成します。
  • 移行テーブルは作成しません。
  • 移行と共に使用することはできません
  • データベースが頻繁に削除および再作成されるようなテストや迅速なプロトタイプのために設計されています。

EnsureCreated を削除します。

context.Database.EnsureCreated();

アプリを実行し、DB がシードされていることを確認します。

データベースを検査する

SQL Server オブジェクト エクスプローラーを使用してデータベースを検査します。 __EFMigrationsHistory テーブルが追加されていることに注意してください。 __EFMigrationsHistory テーブルは、どの移行がデータベースに適用されたかを追跡します。 __EFMigrationsHistory テーブルのデータを表示すると、最初の移行の 1 つの行が表示されます。 前の CLI の出力例の最後のログは、この行を作成する INSERT ステートメントを示しています。

アプリを実行して、すべてが適切に機能していることを確認します。

運用環境で移行を適用する

実稼働アプリケーションでは、アプリケーションの起動時に Database.Migrate呼び出さないことをお勧めします。 Migrate をサーバー ファームのアプリから呼び出すことはできません。 たとえば、アプリがスケールアウト (アプリの複数のインスタンスを実行する) を使用してクラウドに展開されている場合があります。

データベースの移行は、展開の一部として制御された方法で行う必要があります。 実稼働データベースの移行には次の方法があります。

  • 移行を使用して 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 を実行します。

その他の技術情報