January 2016

Volume 31 Number 1

データ ポイント - EF7 の移行: 刷新ではなく確実な機能強化

Julie Lerman | January 2016

Julie LermanCode First Migrations は、Entity Framework 7 で大きな変化を経験しました。基本的な考え方や移行手順は変わりませんが、ワークフローが非常に簡潔でわかりやすくなり、ソース管理と適切に連携するようになりました。

EF7 には 2 種類の移行が用意されています。1 つは、NuGet や多くの .NET プロジェクトで使用されるなじみのある移行で、もう 1 つは ASP.NET 5 アプリと共に DNX ランタイムの一部として実行される移行です。

この 2 種類の移行は、Pluralsight コースの 1 つで紹介しています (bit.ly/1MThS4J、英語)。この Pluralsight コースで使用したベータ 4 バージョンと、今回使用する Release Candidate 1 (RC1) バージョンでは、いくつか変更点がありますが、移行の動作や相違点を学習するのであれば、この Pluralsight コースでも十分です。

ただし、RC1 のリリースは、機能が完成し、まもなく RTM バージョンがリリースされることを表しているため、EF7 の移行について再検討するタイミングが来たと言えます。まずは、Visual Studio のパッケージ マネージャー コンソールでよく使われる Windows PowerShell コマンドから検討しましょう。その後、このようなコマンドを DNX や ASP.NET 5 と一緒に使用する場面について考えます。今回は、読者が EF6 以前のデータベースの初期化と移行の機能についての知識があることを前提としています。

データベースの初期化に機能を詰め込まずに移行を使用する

あまり機能を詰め込むと、負荷が高くなり、制限が生じて、大きな負担がかかります。DbInitializer について言えば、多くの混乱が生み出されていました。そのため、CreateDatabaseIfNotExists の既定の動作に連動する EF DbInitializer が廃止されます。代わりに、開発者が明示的にデータベースを作成 (EnsureCreated) および削除 (EnsureDeleted) できるロジックがあり、統合テストで問題なく使用できます。

EF7 は移行を利用するように設計されています。つまり、移行が既定のワークフローになります。

移行は EF に後付けで追加され、移行を使用する際に若干のインフラストラクチャを構築する必要があったため、"enable-migrations" コマンドが存在していました。このコマンドは新しい API を一切インストールせず、プロジェクト フォルダーを追加し、プロジェクトに新しい DbMigrationConfiguration クラスを 1 つ追加するだけでした。EF7 では、関連ロジックが内部に含まれ、移行の使用を明示的に EF に指示する必要がありません。

以前も説明したように、EF7 は構成可能です。移行を希望しない開発者や移行を必要としない開発者もいるため、移行を行う場合は、移行を含むパッケージをインストールすることによって、移行コマンドを選択する必要があります。

install-package entityframework.commands -pre

EF7 の移行コマンド

Windows PowerShell の Get-Help コマンドレットを使用すると、現在の (RC1 時点の) 移行コマンドが表示されます (図 1 参照)。このリストは、RTM バージョンでも変わらないはずです。

NuGet パッケージ マネージャーの Get-help コマンドで一覧される移行コマンド
図 1 NuGet パッケージ マネージャーの Get-help コマンドで一覧される移行コマンド

Update-Database には、"-script" パラメーターがなくなりました。コマンド名からわかるように、データベースを更新するだけです。いつものように、運用環境でのこのコマンドの使用はお勧めしません。図 2 は、Update-Database コマンドの詳細です。

Update-Database の構文とパラメーター
図 2 Update-Database の構文とパラメーター

スクリプトを作成する場合は、新しい Script-Migration コマンドを使用します。(EF6 で導入された) アイデムポテントなスクリプトを作成するような裏技はなくなりました。Idempotent はパラメーターになっています (図 3 参照)。

Script-Migration のパラメーター
図 3 Script-Migration のパラメーター

移行履歴とモデルのスナップショットに関する大きな変更点

EF7 では、自動移行が削除されています。その結果、移行の管理が単純になり、ソース管理もサポートされるようになります。

移行時に何が行われたかを判断するためには、以前に行った移行の履歴が存在している必要があります。Add-Migration コマンドの実行時は毎回、API によってこの履歴情報が参照されます。以前は、移行時に、_MigrationHistory (最新の名前) というテーブルに、データベースでの各移行のモデルのスナップショットをエンコーディングして格納していました。EF はこのテーブルを使用して、移行で何を行う必要があるかを判断していました。つまり、Add-Migration が実行されるたびに、データベースにアクセスしてこの履歴を読み取ることになるため、多くの問題が生じる原因になっていました。

EF7 では、(Add-Migration によって) 移行が作成されるときに、Migrations フォルダーのいつもの移行ファイルに加えて、現在のモデルのスナップショットを格納するファイルも作成されます。このモデルは、構成に使用する Fluent API 構文を使用して記述します。モデルに変更を加えてから移行を追加すると、そのスナップショットはモデル全体の現在の記述に改訂されます。図 4 に、"MyProperty" という新しい文字列を導入する移行ファイルを示します。

図 4 完全な newStringInSamurai 移行クラス

public partial class newStringInSamurai : Migration
  {
    protected override void Up(MigrationBuilder migrationBuilder)
    {
      migrationBuilder.AddColumn<string>(
        name: "MyProperty",
        table: "Samurai",
        nullable: true);
    }
    protected override void Down(MigrationBuilder migrationBuilder)
    {
      migrationBuilder.DropColumn(name: "MyProperty", table: "Samurai");
    }
  }

以下は、ModelSnapshot クラスの更新部分です。ここでは、"MyProperty" が Samurai クラスの記述に含まれるようになったことがわかります。

modelBuilder.Entity("EF7Samurai.Domain.Samurai", b =>
  {
    b.Property<int>("Id")
      .ValueGeneratedOnAdd();
    b.Property<string>("MyProperty");
    b.Property<string>("Name");
    b.HasKey("Id");
  });

つまり、個々の移行では、通常とは何が変更されているかを記述します。しかし、今度は、モデル全体の記述が必ずプロジェクトに含まれるようになります。つまり、新しい移行を追加するときに、EF は、データベースにアクセスして以前のモデルがどのようになっていたかを調べる必要がなくなりました。必要な情報はすべて手元に用意されています。さらに重要な点は、モデル全体の記述がコードに含まれるようになると、他の任意のファイルと同様に、ソース管理によって差分の取得やマージが可能になり、ソース管理が適切に機能するようになることです。また、情報がチームのメンバーでもアクセスできないデータベースに格納されてしまうこともなくなります。EF7 より前は、ソース管理で移行を管理する適切な方法がありませんでした。EF7 よりも前のバージョンの移行に関するガイダンスは、「チーム環境における Code First Migrations」(bit.ly/1OVO3qr、英語) を参照してください。この資料は、「Grab a coffee, you need to read this whole article」(コーヒーを片手に、最後までお読みください) というアドバイスで始まります。

EF7 では、データベースの移行履歴テーブル (図 5) に、移行 ID と移行を作成した EF のバージョンのみが格納されます。繰り返しになりますが、これは以前のバージョンからの大きな変更点です。以前は、このテーブルにスナップショット自体を格納していました。Update-Database は、このテーブルをチェックして、適用済みの移行を確認し、連続性のない移行も含めて、このテーブルに記載されていない移行を適用します。この操作によってデータベースを操作する必要がある限り、このシナリオで移行がデータベースに簡単な事前チェックを行っても問題はありません。

テーブルに追加された移行を表示する dbo_EFMigrationsHistory
図 5 テーブルに追加された移行を表示する dbo_EFMigrationsHistory

以前の移行がまだデータベースに適用されていない場合移行を追加できないという、あまり意味のないことに対処する必要がなくなりました。これは自動移行の副作用で、多くの開発者が迷路に迷い込む結果になっていました。現在では、必要に応じて自身のソリューション内に移行を構築し、準備ができ次第データベースを更新できるようになりました。

モデルの スナップショットを EF7 がプロジェクト内に格納するようになったからといって、プロジェクトから移行ファイルを削除してはいけません。このような削除を行うと、スナプショットが不適切になります。そのため、移行ワークフローの変化に併せて、Remove-Migration という新しいコマンドが追加されています。移行を追加した後、その移行をデータベースに適用する前に気が変わることもあるので、移行を元に戻すために Remove-Migration が設計されています。このコマンドは、プロジェクト内で 2 つのことを行います。まず、最新の移行ファイルを削除し、その後移行フォルダーのスナップショット ファイルを更新します。興味深いことに、このコマンドではモデルに基づいてスナップショットを再構築しません。代わりに、このコマンドは、各スナップショットと一緒に作成された Designer.cs ファイルに格納されたモデルのスナップショットに基づいて、スナップショットを変更します。この動作をテストしたところ、モデルを変更しなかった (つまり、Samurai クラスから不要なプロパティを削除しなかった) 場合、スナップショットは依然として移行に応じて修正されました。ですが、モデルを自身で修正することも忘れないでください。そうしないと、モデルとスナップショットが同期されなくなり、多くの場合、データベース スキーマとモデルとの同期も失われます。

ちなみに、移行を担当する EF チームのメンバー、Brice Lambson によると、移行ファイルを手動で削除する場合、その直後の Remove-Migration への呼び出しによってこの削除が確認され、別の移行ファイルを削除しないでスナップショットを修正するそうです。

Add-Migration と同様、Remove-Migration もデータベースに影響を与えませんが、データベースの移行履歴ファイルをチェックして、移行が適用済みかどうかをチェックします。適用済みであれば、少なくともまだ Remove-Migration を使用することはできません。

少し混乱させてしまったかもしれません。ですが、気が変わったときに他にできることは、単純に先に進んで移行を追加することだけなので、Remove-Migration には意味があります。これによって、他のチーム メンバーや将来自分自身が混乱する可能性があります。しかし、移行をソース管理に参加させるために支払い小さな代償です。

わかりやすいように、不適切な移行を元に戻す際のガイダンスを示します (図 6 参照)。この表は、"Migration1" がデータベースに適用済みで、"Migration2" は作成されたが、データベースには未適用の状態を想定しています。

図 6 不適切な移行の処理

データベースの状態 コマンド コマンドによって実行されるタスク
Migration2 後に Update-Database を未実行 Remove-Migration

1. Migration2 が履歴テーブルに存在しないことを確認

2. Migration2.cs を削除

3. Snapshot.cs を修正して以前の移行を反映

Migration2 後に Update-Database を実行済み

Update-Database Migration1

Remove-Migration

1. Migration2 の "Drop" メソッド用 SQL の実行

2. 履歴テーブルから Migration2 行を削除

1. Migration2.cs を削除

2. Snapshot.cs を修正して以前の移行を反映

複数の移行を元に戻す必要がある場合は、まず、最後に成功した移行の名前を指定して Update-Database を呼び出します。次に、Remove-Migration を呼び出して、移行とスナップショットを一度にロール バックします。

移行に関するここまでの説明はすべて、移行がモデルへの変更のリフレクションにすぎないことを示しています。したがって、モデルへの変更を手動で元に戻した場合、その後このような変更を反映するすべての移行を削除しなくてなりません。

Scaffold-DbContext によるリバース エンジニアリング

以前のバージョンの EF は、データベースを Code First モデルにリバース エンジニアリングする機能が提供されていました。マイクロソフトは、Entity Framework Power Tools によってこの機能を利用可能にしており、EF6 からは EF Designer でもこの機能を利用できるようになっています。よく使われている (無料の) 拡張機能、EntityFramework Reverse POCO Generator (reversepoco.com、英語) など、サードパーティ製のツールもあります。EF7 にはデザイナーがないため、このジョブを実行するために、スキャフォールディング コマンドが作成されました。

以下に、Scaffold-DbContext のパラメーターを示します。

Scaffold-DbContext [-Connection] <String> [-Provider] <String>
                   [-OutputDirectory <String>] [-ContextClassName <String>]
                   [-Tables <String>] [-DataAnnotations] [-Project <String>]
                   [<CommonParameters>]

Fluent API の構成が好みなので、前回同様これらは既定の設定で、データ注釈を使用する場合は DataAnnotations フラグを指定して選択する必要があるのは筆者の好みに合っています。以下は、このパラメーターを使用して、既存のデータベースからリバース エンジニアリングするコマンドです (スキャフォールディングするテーブルを選択することもできますが、ここでは指定していません)。

Scaffold-DbContext
   -Connection "Server = (localdb)\mssqllocaldb; Database=EF7Samurai;"
   -Provider entityframework.sqlserver
   -OutputDirectory "testRE"

図 7 に示すように、このコマンドによって、新しいフォルダーと複数のファイルがプロジェクトに追加されます。重要な機能なので、このようなクラスについてたくさんのことを調べましたが、詳細は今後のコラムで取り上げるつもりです。

Scaffold-DbContext の結果
図 7 Scaffold-DbContext の結果

DNX バージョンの移行コマンド

パッケージ マネージャー コンソールで実行するコマンドは Windows PowerShell コマンドです。この Windows PowerShell は当然 Windows の一部です。ASP.NET 5 と EF7 の最も重要な特徴の 1 つは、Windows に依存しなくなったことです。DNX とは .NET の簡易実行環境で、Windows だけでなく OS X や Linux も実行することができます。詳細については、bit.ly/1Gu34wX (英語) を参照してください。entityframework.commands パッケージには、DNX 環境用の移行コマンドも含まれています。ここからは、このような移行コマンドについて簡単に見ていきます。

Visual Studio 2015 を使用している場合、これから示すように、コマンドをパッケージ マネージャー コンソールで直接実行できます。それ以外の場合は、関連する OS のコマンドラインから実行します。Mac で EF7 のコーディングや移行を行う場合は、Nate McMaster の Channel 9 ビデオをご覧ください (bit.ly/1OSUCdo、英語)。

まず、正しいフォルダー (モデルが存在するプロジェクト フォルダー) に移動します。既定のプロジェクト ドロップダウンは、ファイル パスに関連付けられていません。dir コマンドを使用して現在いる場所を確認してから、cd コマンドを使用して正しいフォルダーに移動します。ここでは、Tab 補完が便利です。図 8 の既定のプロジェクトは src\EF7WebAPI ですが、このディレクトリで dnx コマンドを実行するには、EF7 モデルを含むこのプロジェクトのパスに、明示的にディレクトリを変更しなければなりません。

DNX コマンド実行前の正しいディレクトリへの移動
図 8 DNX コマンド実行前の正しいディレクトリへの移動

ディレクトリに移動したら、コマンドを実行します。すべてのコマンドの先頭には dnx を付ける必要があります。dnx ef を使用すると、利用可能なコマンドをすべて一覧できます。ef は、project.json でセットアップしたショートカットです。このようなセットアップの詳細については、Pluralsight ビデオ「Looking Ahead to Entity Framework 7」(bit.ly/PS_EF7Alpha、英語) をご覧ください。

中核となるコマンドは、database、dbcontext、および migrations です。migrations には以下のサブコマンドがあります。

add     Add a new migration
list    List the migrations
remove  Remove the last migration
script  Generate a SQL script from migrations

各コマンドには、以下のように --help を追加してクエリできるパラメーターがあります。

dnx ef migrations add --help

たとえば、移行の名前が initial の場合、コマンドは以下のようになります。

dnx migrations add initial

コラムの前半でも同じコマンドを使用しました。このコマンドには、プロジェクト、DbContext などの詳細を指定できるパラメーターがあります。script コマンドには、前述のように idempotent パラメーターがあります。ただし、dnx の script コマンドには相違点が 1 つあり、既定では、スクリプトがコマンド ウィンドウにリストされるだけです。スクリプトをファイルに出力するには、出力の <file> パラメーターを明示的に指定する必要があります。

以下のコマンドは、移行をデータベースに適用します。

dnx ef database update

dbcontext コマンドには 2 つのサブコマンドがあります。1 つは dbcontext list で、プロジェクト内で見つかったすべての DbContexts をリストします。これが用意されているのは、Windows PowerShell でコマンドを使用するときのように簡単にタブ拡張を利用できないためです。もう 1 つのサブコマンド scaffold は、前述の DbContext-Scaffold コマンドの dnx バージョンです。

EF7 で改善された全体的な移行エクスペリエンス

自動移行が削除されたことによって、EF7 では移行が簡素化されています。基本的な考え方は同じですが、ワークフローが大幅に簡潔になっています。チーム間の障壁を取り除いて、チーム環境で移行を使用できることが重要です。モデルのスナップショットをソース コードにすることによって、これが解決されています。スキャフォールディング コマンドによってリバース エンジニアリングを可能にしているのは、デザイナーに頼ることなくこの重要な機能を提供する優れた方法です。

EF チームの 7 月 23 日の設計ミーティング メモ (bit.ly/1S813ro、英語) には、名前付けの規約を厳しくした後の Windows PowerShell コマンドと dnx コマンドの関係を示したすばらしい表が示されています。最終的には、もう少し明確なバージョンがドキュメントに記載されると考えていますが、このままでも役に立つのは確かです。

このコラムは、EF7 の RC1 バージョンを用いて執筆しましたが、このバージョンの機能は完成していると考えられるため、ここに記載した情報は、2016 年初旬に ASP.NET と一緒にリリースされる EF7 におそらく一致しています。


Julie Lerman は、バーモント ヒルズ在住の Microsoft MVP、.NET の指導者、およびコンサルタントです。世界中のユーザー グループやカンファレンスで、データ アクセスなどの .NET トピックについてプレゼンテーションを行っています。彼女のブログは thedatafarm.com/blog (英語) で、彼女は O'Reilly Media から出版されている『Programming Entity Framework』(2009 年) および『Code First』版 (2011 年)、『DbContext』版 (2012 年) を執筆しています。彼女の Twitter (@julielerman、英語) をフォローして、juliel.me/PS-Videos (英語) で彼女の Pluralsight コースをご覧ください。

この記事のレビューに協力してくれたマイクロソフト技術スタッフの Brice Lambson に心より感謝いたします。