次の方法で共有


Entity Framework

Entity Framework 6: 上級者向けエディション

Julie Lerman

Entity Framework の最新メジャー リリースの EF6 により、マイクロソフトのオブジェクト リレーショナル マッピング (ORM) ツールは "上級者向けツール" という新たなる高みに達し、長年利用されてきた .NET ORM ツールのような初心者向けツールではなくなりました。EF は全面的に進化し、これまでのツールを上回る魅力的なツールになっています。

データベース開発者向けのツールとして始まった Entity Framework は、当初、.NET コミュニティの中でもアジャイル開発者の激しい怒りを買うという波乱含みの幕開けでした。そこで、アプリケーション開発に干渉しない手法を検討し、単純な従来の CLR オブジェクト (POCO) モデルに移行したことで、データを中心に扱う開発者をおびやかすことがなくなり、テストやドメイン型のソフトウェア開発が可能になりました。Entity Framework はパフォーマンス上の問題や、生成後のコード品質についてのさまざまな懸念事項に対処しながら、多くのデータベース管理者 (DBA) の賛同を得てきました。

マイクロソフトは EF 4.1 の当初からその複雑さが必ず問題になるだろうと認識していたため、DbContext API を導入して機能へのアクセスをシンプルにしています。同じく、デザイナーや生成済みコードの使用が好まれないこともあるため、独自のコードでモデルを構築できる機能を用意しました。このような進化の過程で、機能、構文、コード、パフォーマンス以外にも大きな変更が加えられています。EF チームは、コミュニティのユーザーと透明性の高い双方向のコミュニケーションを持つようになり、機能のリリースを Microsoft .NET Framework のリリースまで待つのではなく、もっとタイムリーに行うようにしました。その結果、2012 年の EF5 リリース以後、2 つの進化が生まれています。1 つは、Entity Framework の API がすべて .NET Framework から抜き出され、同じくチームで取り扱っていた (メジャー リリースとリリース時期を同期しない) 機能 API に組み込まれたことです。もう 1 つは、開発作業全体がオープン ソース モデルに移行したことです。EF6 は entityframework.codeplex.com(英語) で一般に公開された状態で開発が行われています。議事録、チェックイン、およびダウンロード可能な夜間ビルドからチームの動向を確認できるだけでなく、EF6 にソースを提供することもできます (ただし、EF チームは後者を完全に見落としていました)。

F6 は進化が結実したもので、革新的とはいえません。Entity Framework モデルの構築方法、アプリケーションでの EF の使用方法など、EF でよく知られていることにはほとんど変更がありません。EF6 により ORM は進化していますが、基本的なしくみは変わっていません。これまで行ってきた EF の学習努力はこれからも無駄にはなりません。EF6 にはもちろんいくつか新たな変更点がありますが、それは名前空間に対する多少の変更に限定されているため、準備をしておけば簡単に対処できます。このコラムの最後に最新の変更関連の資料を紹介しておきます。

EF6 の機能は、以下のカテゴリに分類されます。

  1. 無料で利用できる機能: 中核の一部になる機能です。これらの機能からメリットを得られるのはもちろんですが、新しいコーディングを学ぶことも必要ありません。このグループには、ビュー生成エンジンの作り直しやクエリ コンパイルの変更などのパフォーマンスの強化、既に開いている接続を使用する DbContext 機能の安定性、Entity Framework が作成する SQL Server データベース設定の変更などが含まれます。
  2. レベルが設定された機能: 大きな機能強化として、これまでデザイナーで作成されるモデルがサポートしていたストアド プロシージャへのマッピングが Code First でサポートされるようになります。この機能は、Channel 9 ビデオ (bit.ly/16wL8fz(英語) など) や CodePlex サイトの詳細仕様で数多く取り上げられているため、ここでは改めて説明しません。
  3. 他にも興味深い変更があります。既に説明したように、EF6 では EF API が .NET Framework から抜き出され、NuGet パッケージに完全にカプセル化されるようになりました。そのため、列挙型や空間データのサポート、パフォーマンスの向上など、EF5 から導入される特定の機能は .NET 4.5 には依存しなくなります。したがって、.NET 4 と EF6 を併用する場合でも、結果として、これらの機能を効果的に使用できます。
  4. EF Designer もこのカテゴリに分類されます。EF Designer は Visual Studio 2013 には含まれていませんが、Visual Studio の拡張機能として提供されています。EF6 では、デザイナーを拡張機能として使用することに大きなメリットがあります。今後は、現在 Entity Framework Power Tools で提供されている機能も含め、チームがデザイナーに直接機能を追加できるようになります。デザイナーを Visual Studio から切り離すことで、マイクロソフトは EF6 ツールを Visual Studio 2012 にも、Visual Studio 2013 にも同梱することができました。
  5. 上級者向け機能: 上級者向け機能とは、以前、EF の基本サンプル アプリケーションを使用していたときから強く求められていた機能です。このような機能は EF6 にたくさんあり、非同期クエリと保存のサポート、Code First のカスタム規約の復活、新しい DbConfiguration 型による拡張性の向上 (低レベルの EF6 IDbDependencyResolver に依存します)、単体テストでのモック作成のサポート、不安定な接続での構成可能な再試行機能などが挙げられます。これらの機能を使用するのに認定資格を取得した上級者である必要はなく、実際に機能を使用するときに上級者になったように感じられます。

また、他にも特殊なカテゴリがあります。それは、コミュニティ メンバーによる EF6 への貢献です。Unai Zorrilla は、DbSet.AddRange と RemoveRange、複数形化をカスタマイズする機能、および便利な DbChangeTracker.HasChanges メソッドを EF6 に追加しました。彼は、今後のバージョンの EF 向けに、他にも優れた機能の作成に取り組んでいます。Erik Jensen は、SQL Server Compact (SQLCE) MVP であり、LINQ to Entities クエリで SQL Server 関数を使用する SqlFunctions に似た SQLCeFunctions に携わってきました。Alireza Haghshenas と VSavenkov という CodePlex メンバーによって、EF のビュー生成速度が大幅に向上しました。これは、大規模で複雑なモデルほど効果が高くなります。また、Iñaki Elcoro (CodePlex では iceclow とも呼ばれます) により、カスタム移行操作を定義できるようになりました (EF チームの Rowan Miller は、この機能に関するブログ記事をいくつか投稿しています。最初のブログ記事については、bit.ly/ZBU0w1(英語) を参照してください)。貢献者の一覧については、チームのブログ記事「EF6 RTM Available」(EF6 RTM のリリース、bit.ly/1gmDE6D、英語) を参照してください。

今回は、一般にあまり取り上げられていないトピックについて詳しく説明し、それ以外は学習に役立つ公開済みのリソースを紹介します。

MSDN のデータ アクセス デベロッパー センター (msdn.microsoft.com/ja-jp/data/jj574253) の [バージョン履歴] ページには、すべての機能が一覧になっています。それぞれのリリースには 1 ~ 2 文の情報が掲載されており、詳細情報へのリンクが用意されているものもあります。

パフォーマンスの向上と安定性に対する成果

パフォーマンスは多くのソフトウェア プロジェクトの悩みの種であり、Entity Framework はリリース当初からパフォーマンスについて多くの批判を受けてきました。しかし、リリースのたびにパフォーマンスの面で大幅な改善を行ってきました。

パフォーマンスに関する重大な問題の 1 つは、アプリケーション プロセスでコンテキストを初めて使用するときに生じる起動時間です。ただし、この起動時間を改善する対処方法はたくさんあります。私が執筆した記事や、パフォーマンスに関する考慮事項についての MSDN 記事 (msdn.microsoft.com/ja-jp/library/cc853327.aspx) などのリソースで、関連するテクニックを探してください。

パフォーマンスの妨げになることが多い起動手順は、マッピング ビューのビュー生成です。この生成では、EF がモデル内の各エンティティ セットに対してクエリを行う関連 SQL を作成します。これらのビューでは起動時に作成したクエリを利用して、EF が特定のクエリ用 SQL を実行時に作成する必要がないようにしています。ビュー生成は、モデルを EF Designer で作成した場合でも、Code First で作成した場合でも行われます。これらのビューを事前に生成してアプリケーションにコンパイルしておけば起動時間を短縮できます。

大規模で複雑なモデルでは、ビュー生成は特に手間のかかる作業です。EF6 ではこのプロセスが改善されています。その結果、ビューを事前に生成する場合でも、実行時に生成する場合でも速度は大幅に向上します。EF 6.0.0 リリースには、この機能を妨げるバグがあるので注意してください。ただし、このバグは EF 6.0.1 では修正されています。EF 6.0.1 は EF 6.0.0 と同日にリリースされ、(作成時に) NuGet から既定のパッケージとして入手できます。また、EF でこれらの生成済みビューを実行時に使用する方法が強化され、クエリの実行時間が改善されています。ビュー生成は、小さなモデルまたは単純なモデルでは問題になることはありません。ただし、多くの組織が、大量のエンティティを含むモデルを使用しており、これらのエンティティには継承やリレーションシップなどの複雑な関係が含まれます。このような組織は、この変更により多くのメリットを得ることになります。

その他のパフォーマンスに関する注意事項については、EF6 リリースに関するお知らせのブログ記事 (bit.ly/1gmDE6D、英語) で、Entity Framework アセンブリで Ngen を使用する方法に関するガイダンスを参照してください。

LINQ Contains の高速コンパイル: EF チームはクエリの作成方法について微調整を続ける中で、ある 1 つの変更に注目しました。それが、LINQ Contains を使用したクエリのコンパイル方法です。具体的には、コンパイル プロセスのパフォーマンスが向上します。生成される SQL には変わりはないため、データベースでのクエリの実行には影響しません。

SQL Server データベースの作成: EF6 の安定性に関する強化として、データベース作成に関連する強化が行われています。Model First のワークフローでも Code First のワークフローでもデータベースを作成できます。このデータベースが SQL Server の場合、EF は SQL Server データベースの "ベスト プラクティス" に合わせられるようになります。そのため、データベースの READ_COMMITTED_SNAPSHOT 設定は ON に構成されます。つまり、データベースは変更が行われるたびに既定でデータベース自体のスナップショットを作成します。実際のデータベースで更新が行われている間、クエリはそのスナップショットで実行されます。この機能の詳細については、私の最新のブログ記事「What’s that Read_Committed_Snapshot Transaction Support for EF6 About Anyway?」(EF6 の Read_Committed_Snapshot トランザクション サポートの概要、bit.ly/14FDpZI、英語) を参照してください。

開いている接続の再利用: 最終的に、不満の元だった制限事項がなくなりました。EF6 では、開いている DbConnection でコンテキスト呼び出しを実行できます。これまでは、明示的に接続を開いてからその接続を使用する EF コマンドを実行する場合、または別のコンテキスト呼び出しによって既に開かれている接続を再利用する場合、"EntityConnection の構築に使用できるのは、閉じている DbConnection のみです" というメッセージと共に例外がスローされました。EF6 では既に開いている接続を再利用できるため、快適に操作を行うことができます。

上級者向けの機能強化

非同期サポート: 2013 年 3 月のデータ ポイントのコラム「EF6 Alpha の操作」(msdn.microsoft.com/magazine/jj991973、英語) で、非同期クエリ、SaveChanges メソッド、カスタム規約など、いくつか新機能を紹介しました。

非同期サポートにより、.NET 4.5 の Await/Async パターンが EF の LINQ クエリ実行メソッドに適用され、FirstAsync、FirstOrDefaultAsync、SingleAsync、SingleOrDefaultAsync、ToListAsync、ForEachAsync などのメソッドを使用することができます。これらのメソッドの一覧を確認するには、System.Data.Entity.QueryableExtensions を確認してください。DbSet には FindAsync、DbContext には SaveChangesAsync があります。上記の記事から変わったことはほとんどないため、詳細についてはこの記事を参照してください。また、マイクロソフトはさまざまなチュートリアルや興味深い詳細仕様書を作成しています。これらは上記の [バージョン履歴] ページから入手できます。

カスタム コード規約: 私は別の上級者向けの機能に関する記事で、Code First のカスタム規約についても執筆しました。EF は、Code First の初期リリースでこの規約に取り組んでいましたが、この規約が原因でリリースが延期になり、チームはこの規約の作業を中断せざるを得ませんでした。このことは、多くの開発者を落胆させました。

たとえば、エンティティやプロパティに一般的なルールとして共通のマッピングを適用するとします。モデルの各エンティティまたはプロパティに個別にマッピングを指定する必要はなく、規約として定義し、全体に適用することができます。たとえば、データベース プロバイダーが既定で使用するプロパティではなく、各文字列プロパティをデータベースに 50 文字で表示する場合は、このルールを規約として指定できます。規約では Code First Fluent API を利用するため、次の方法でマッピングを構成したことがあれば、なじみがある方法で規約を構築できます。

modelBuilder.Properties().Configure(p => p.HasMaxLength(50))

これで、このモデルのすべての文字列がデータベース列に 50 文字でマッピングされます。Fluent や注釈の構成と同じように、プロパティやエンティティの規約を指定し、継承のマッピングを制御できます。規約を使用してリレーションシップを構成すると、モデルベースの規約で処理することになり、一般的でなく複雑なタスクになります。詳細については、bit.ly/1gAqcMq(英語) を参照してください。規約をデータ注釈として使用することもできます。Code First でモデルを構築している場合は、規約の実行時に階層が適用されます。既定では、組み込みの規約が最初に実行され、カスタム規約は後から実行されます。ただし、組み込みの規約より先にカスタム規約を実行するように指定できます。詳細については、「カスタム コードを最初の規約に設定する」(bit.ly/14dg0CP、英語) を参照してください。

接続の回復性: EF でクエリの実行や変更の保存を行うときに接続が失われた場合、EF に再試行を要求できます。接続が失われることは企業のイントラネットで問題になります。一方で、接続の回復性は、クラウドに接続するアプリをサポートするうえで非常に役立つことがわかっています。このような再試行は IDbConnectionStrategy を使用して構成できます。EF に含まれる SQL Server プロバイダーによって default:SqlServerExecutionStrategy が指定され、一時的な接続によってスローされる例外の処理方法を調整する必要があることを示すエラー メッセージが表示されます。他にも、SqlAzureExecutionStrategy を使用すると、Windows Azure SQL データベースへの接続に合わせて調整されます。

処理方法を指定するには、新しい DbConfiguration クラスを使用するのが最も簡単です。このクラスを使用すると、特定のデータベース プロバイダーの動作を簡単に構成できます。以下のコードでは、SqlClient に対して SQLAzureExecutionStrategy を使用するように指定しています。

SetExecutionStrategy (SqlProviderServices.ProviderInvariantName,
   () => new SqlAzureExecutionStrategy());

接続方法を構成できるだけでなく、独自の接続方法を作成したり、必要に応じてプログラムで接続を中断することもできます。EF チーム メンバーの Miller は、この中断方法について自身のブログ記事 (bit.ly/14gPM1y、英語) で説明しています。

別の EF チーム メンバーの Glenn Condron に教えてもらったテクニックを使用して、SqlAzureExecutionStrategy をテストしました。この接続方法で検出する、特定の一時的な接続に関するエラー コードをトリガーするため、新しい EF6 コマンドのインターセプト機能を使用して一時的な接続のエラーをスローしました。続いてテストを実行すると、実行方法を設定したとき、最初のエラーの後にクエリが 5 回再試行されたことを示す出力が確認されました。私のブログ記事では、この機能について、ある開発者がすばらしいコメントを投稿しています。彼の会社では、この機能によるメリットを既に確認できているそうです (bit.ly/HaqMA0、英語)。

別のスレッドでの再試行を同時に実行しないようにする興味深いアルゴリズムも公開しています。

DbTransaction と DbConnection の共有: ここまでの内容から、EF ではデータベースに対する呼び出しで常に既定で DbTransaction を使用することにお気付きでしょう。たとえば、SaveChanges を呼び出す場合、最初のコマンドがデータベースに送信される前に、DbTransaction が作成されます。次に、EF は必要な挿入、更新、および削除のコマンドをすべてデータベースに送信し、最後にトランザクションをコミットします。いずれかのコマンドが失敗した場合、それまでに実行したコマンドはすべてロールバックされます。

これまでは、TransactionScope をスピンして、同じトランザクションで処理する必要がある EF 呼び出しとその他の呼び出し (データベースや EF 関連の呼び出しとは限りません) をラップすることで、この既定の動作をいつでもオーバーライドすることができました。EF6 に機能が追加されたことで、単一の DbTransaction で複数のデータベース呼び出しに対応することができます。ただし、トランザクション、または複数のデータベースに対する呼び出しで使用する分散トランザクション内に、データベース以外のロジックを含める場合は、TransactionScope を使用する必要があります。

DbTransaction を EF6 と共有するには、現在の DbTransaction と UseTransaction メソッドに対する参照を返す、新しい BeginTransaction メソッドが必要です。

次のコードは既定の動作をデモします。

//code to create two new casinos, "casino1" & "casino2"
 var context = new CasinoSlotsModel();
 context.Casinos.AddRange(new[] { casino1, casino2 });
 context.SaveChanges();
 context.Database.ExecuteSqlCommand
   ("Update Casino.Casinos set rating= " +
  (int) casino.Rating)

プロファイラーは、2 つの挿入をトリガーした SaveChanges と、更新をトリガーした ExecuteSqlCommand の各コンテキスト呼び出しで使用されるトランザクションを示しています (図 1 参照)。


図 1 固有のトランザクションにラップされた個別のコンテキスト呼び出しのコマンド

次に、トランザクション (ラップされた SaveChanges 呼び出しと ExecuteSqlCommand 呼び出し) を共有するようにコードを変更します。新しい DbContext.Database.BeginTransaction メソッドを使用して、System.Data.Entity.DbContextTransaction のインスタンスを明示的に作成し、必要に応じて接続を開きます (DbContext.Database.Connection を使用する類似のコマンドがありますが、これは EF コマンドで共有できない System.Data.Common.DbTransaction を返すため注意してください)。

using (var tx = context.Database.BeginTransaction()) {
   try  {
     context.SaveChanges();
     context.Database.ExecuteSqlCommand
       ("Update Casino.Casinos set rating= " +
       (int) casino.Rating);
     tx.Commit();
   }
   catch (Exception)  {
     tx.Rollback();
   }
 }

図 2で、すべてのコマンドが同じトランザクションにラップされていることを確認できます。


図 2 単一のトランザクションにラップされたすべてのコンテキスト呼び出しのコマンド

他にも、UseTransaction という新機能があります。DbTransaction をスピンして ADO.NET 呼び出しで使用するとき、DbContext.Database.UseTransaction を使用すると、同じトランザクション内の別のコンテキスト インスタンスから EF 呼び出しを実行できます。この例は bit.ly/1aEMIuX(英語) で確認できます。

EF は既に開いている接続で EntityConnection を作成できるようになるため (この処理は、ObjectContext によってバックグラウンドで実行されます)、開いている接続を明示的に再利用することもできます。また、コンテキストが閉じることができる接続は、そのコンテキストで開いた接続のみです。コンテキストの接続を開いてからコンテキストの呼び出しを実行する簡単なテストを次に示します。

[TestMethod]
 public void ContextCanCreateEntityConnectionWithOpenConnection()
 {
   using (var context = new CasinoSlotsModel())
   {
     context.Database.Connection.Open();
     Assert.IsNotNull(context.Casinos.ToList());
   }
 }

ToList 呼び出しを実行するとき、DbContext が ObjectContext インスタンスを作成し、このインスタンスが EntityConnection を作成します。次に、DbContext が EntityConnection を使用して DbConnection を開き、すべての結果が返されてこの呼び出しが完了すると DbConnection を閉じます。この処理を EF5 で実行すると例外が発生しますが ("EntityConnection の構築に使用できるのは、閉じている DbConnection のみです" というメッセージが表示されます)、EF6 では動作の変更があったため適切に実行されます。この変更により、接続状態をより細かく制御する必要があるシナリオで、接続を再利用できます。この仕様は、"接続状態を保証できないコンポーネント間の接続を共有するシナリオ" などでの使用を推奨します。

AddRange および RemoveRange: 既に述べたように、AddRange および RemoveRange はコミュニティ メンバーの Zorrilla が追加したメソッドです。各メソッドはパラメーターとして列挙可能な単一のエンティティ型を受け取ります。DbTransaction の共有に関するセクションの最初のコード サンプルでは、Casino インスタンスの配列を渡すときに AddRange を使用しました。

context.Casinos.AddRange(new[] { casino1, casino2 });

Entity Framework は既定で Add メソッドと Remove メソッドのそれぞれで DetectChanges を呼び出すため、上記のようなメソッドは、一度に 1 つのオブジェクトを追加または削除するよりもはるかに早く実行されます。Range メソッドを使用すると、DetectChanges を一度呼び出すだけで複数のオブジェクトを処理でき、パフォーマンスが大幅に向上します。このメソッドを 5 個、50 個、500 個、5,000 個、および 50,000 個のオブジェクトを使用するシナリオでテストしましたが、少なくとも私がテストしたシナリオでは配列のサイズに制限は見られず、どのサイズでも驚くほど高速にオブジェクトを処理できました。ただし、このようなパフォーマンスの向上は、オブジェクトに基づいてコンテキストを処理する場合にのみ見られ、SaveChanges とは何の関係もありません。SaveChanges の呼び出しが一度に実行するデータベース コマンドは 1 つのみです。そのため、50,000 個のオブジェクトをコンテキストにすばやく追加できる一方で、SaveChanges を呼び出すときには 50,000 個の挿入コマンドを個別に実行することになります。おそらく、このような処理を実際のシステムには取り入れたくないでしょう。

その一方で、オブジェクトを EF で追跡する必要のない一括操作 (bit.ly/16tMHw4、英語) や、データベースに対する単一の呼び出しで複数のコマンドをいっせいに送信できる一括操作 (bit.ly/PegT17、英語) に関するサポートの実装については長い間議論が行われてきました。いずれの機能も EF6 リリースには組み込まれていませんが、両方とも重要な機能なので今後のリリースでの導入を予定されています。

コーディング スタイルへの干渉の軽減: .NET では、System.Object.Equals メソッドをオーバーライドして、等値かどうかを確認するシステム ルールを定義できます。ただし、Entity Framework には、追跡したエンティティノ等値性を確認する固有の方法があります。この方法では、ID を使用します。Equals (および Equals が依存している GetHashCode メソッド) を上書きした場合は、Entity Framework の変更追跡の動作が適切に処理されない可能性があります。Petar Paar は、この問題を自身のブログ記事 (bit.ly/GJcohQ、英語) で非常にわかりやすく説明しています。EF6 ではこの問題を解決するため、変更された可能性があるカスタムの Equals および GetHashCode のロジックは無視して、固有の Equals および GetHashCode のロジックを使用して変更追跡のタスクを実行します。ただし、ドメイン ロジックでカスタム メソッドを明示的に呼び出すこともできます。このように EF6 には 2 つの手法が用意されています。

グラフや集計に重点をおくと、ある型を他の型に入れ子にすることを考える場合があります。ただし、Code First モデル ビルダーは、入れ子になっている型を検出し、モデルでエンティティや複雑な型を作成することはできませんでした。図 3 は入れ子になっている型の Address の例を示しています。Address は Casino 型でのみ使用するため、Casino クラスに入れ子にしました。また、Address は固有の ID が必要ないため、ドメイン駆動設計 (DDD) の値オブジェクトとして作成しました (DDD の値オブジェクトの詳細については、私の 2013 年 10 月のデータ ポイントのコラム「ドメイン駆動設計のコーディング: データを重視する開発者のためのヒント (第 3 部)」(msdn.microsoft.com/magazine/dn451438) を参照してください)。

図 3 Address 型が入れ子になっている Casino クラス

public class Casino()
    {
      //...other Casino properties & logic
      public Address PhysicalAddress { get; set; }
      public Address MailingAddress { get; set; }
      public class Address:ValueObject<Address>
      {
        protected Address(){    }
        public Address(string streetOrPoBox, string city,
                       string state,string postalCode)
        { City = city;
          State = state;
          PostalCode = postalCode;
          StreetOrPoBox = streetOrPoBox; }
        public string StreetOrPoBox { get; private set; }
        public string City { get; private set; }
        public string State { get; private set; }
        public string PostalCode { get; private set; }
      }
    }

{ protected Address(){ } public Address(string streetOrPoBox, string city, string state,string postalCode) { City = city; State = state; PostalCode = postalCode; StreetOrPoBox = streetOrPoBox; } public string StreetOrPoBox { get; private set; } public string City { get; private set; } public string State { get; private set; } public string PostalCode { get; private set; } } }

EF Power Tools を使用してモデルを視覚的に表示し、図 4 では、EF5 および EF6 を使用した場合の Casino エンティティを示しています。EF5 では入れ子になっている型は認識されず、モデル内に Address や依存プロパティ (PhysicalAddress や MailingAddress など) は含まれません。一方で、EF6 では入れ子になっている型を検出でき、モデル内に Address フィールドが表示されているのを確認できます。


図 4 EF5 と異なり、入れ子になっている Address 型を確認でき、依存プロパティが含まれる EF6

このように入れ子になっている型を有効にする内部的な変更によって、別の問題も解決されます。それは、同じプロジェクトの異なる名前空間で同じ名前の複数の型を使用する場合に発生する問題です。これまでは、EF で EDMX からメタデータを読み取り、アセンブリで一致する型を探す場合、名前空間は考慮されませんでした。

これが原因で問題が発生する一般的なシナリオでは、エンティティを含むモデルと同じプロジェクトに、EF 以外のエンティティの表現が含まれていました。たとえば、次のようなコードで生成される PokerTable クラスをモデルから使用するとします。

namespace CasinoEntities
 {
   public partial class PokerTable
   {
     public int Id { get; set; }
     public string Description { get; set; }
     public string SerialNo { get; set; }
   }
 }

また、モデルの一部ではありませんが、同じプロジェクト内の次のような DTO クラスも使用するとします。

namespace Casino.DataTransferObjects
 {
   public class PokerTable
   {
     public int Id { get; set; }
     public string Description { get; set; }
     public string SerialNo { get; set; }
   }
 }

プロジェクトの対象が EF5 となっている場合、プロジェクトのビルド時に次のエラーが表示されます。

CLR 型から EDM 型へのマッピングがあいまいです。EDM 型 'PokerTable' に一致する CLR 型が複数あります。前に検出された CLR 型は 'MyDtos.PokerTable'、新たに検出された CLR 型は 'EDMXModel.DTOs.PokerTable' です。

このエラーは EDMX でのデザイン時に表示されます。Code First で同じシナリオ (名前空間は異なるが名前が同じで一致するクラスが 2 つあり、いずれかのクラスがモデルに含まれるシナリオ) を使用する場合、この問題は実行時、モデル ビルダーが Code First モデルの解釈を開始するときに確認されます。

この問題は長い間ストレスの元になっていました。表示されたメッセージを読んで、コンピューターに別の名前空間は見つからないものかとたずねたくなるほどです。また、私は、同じ問題に直面した友人、クライアント、他の開発者の方からたくさんの問い合わせを受けています。

EF6 では名前空間を認識し、このようなシナリオに対応できるようになっています。以上の 2 つの変更を可能にした内部機能の詳細については、Arthur Vickers のブログ記事 (bit.ly/Wi1rZA、英語) を参照してください。

コンテキスト構成をコードへ移行: app.config ファイルまたは web.config ファイルに適用して、コンテキストのしくみを定義する設定にはたくさんの種類があります。これらの設定を使用すると、データベースの初期化や移行、既定のデータベース プロバイダーなどを指定できます。設定にはたくさんの文字列が含まれ、覚えるのが難しいため、これらを追加するにはコピーして貼り付けるのがほとんどです。EF6 を使用すると、DbConfiguration クラスを使用して、コードでたくさんのコンテキスト構成を宣言できます。この方法については 3 月のデータ ポイントのコラムで紹介しましたが、競合状態が原因のバグが見つかりました。このバグは報告され、既に修正されています。そのため、ここでもう一度 DbConfiguration クラス (コード ベースの構成とも呼ばれます) について説明します。モデル変更時のデータベース移行について定義する場合、Code First、DbConfiguration の設定、DbMigrationConfiguration の構成マッピングに触れると混乱が生じる可能性が高いため注意してください。DbConfiguration は DbContext 設定を対象としています。

DbConfiguration は、別の上級者向けの低レベルの EF6 機能に依存しています。この機能とは、依存関係の解決のサポートで、ASP.NET MVC および Web API で使用される IDependencyResolver に似ています。依存関係の解決により、サービス ロケーター パターンと制御の反転 (IoC: Inversion of Control) パターンをコードで共に使用できます。このとき、EF6 は、共通インターフェイスを実装する使用可能なオブジェクトの階層から選択できます。この場合、ルート インターフェイスは IDbDependencyResolver です。EF6 にはコンテキスト設定の優先順位を検出および配置できるたくさんの DbConfiguration が含まれますが、依存関係の解決を利用して EF に新機能を追加することも可能です。IDbDependencyResolver の詳細については、bit.ly/QKtvCr(英語) を参照してください。このページでは、構成の例をいくつか紹介し、機能の仕様について説明します。

DbConfiguration を使用してなじみのあるコンテキスト ルールや、EF6 の新しい設定を指定することができます。図 5 は、EF で既定の動作の代わりに使用する、さまざまな設定を含むサンプルの構成クラスを示しています。設定がクラス コンストラクターに配置されていることに注目してください。

図 5 サンプルの構成クラス

public class CustomDbConfiguration : DbConfiguration
 {
   public CustomDbConfiguration()
   {
     SetDefaultConnectionFactory(new LocalDbConnectionFactory("v11.0"));
     SetDatabaseInitializer
       (new MigrateDatabaseToLatestVersion());
     //SetDatabaseInitializer(new MyInitializer());
     SetExecutionStrategy("System.Data.SqlClient", 
       () =&gt; new SqlAzureExecutionStrategy());
     AddInterceptor(new NLogEfCommandInterceptor());
     SetPluralizationService(new CustomPluralizationService());
   }
 }

SetDefaultConnectionFactory が、構成ファイルの entityframework セクションで使用している可能性がある DefaultConnectionFactory タグに取って代わります。SetDatabaseInitializer は、構成ファイルやアプリケーションの起動時の初期化または移行の構成を指定しているコードに取って代わります。ここでは 2 つの例を紹介しています ( 1 つはコメントにしています)。SetExecutionStrategy には、EF クエリやその他の実行コマンドの途中で接続が失われた場合の実行内容を指定できます。SetPluralizationService では、EF6 のもう 1 つの新機能である、カスタムの複数形化を作成する機能を公開します。この機能の詳細については、もう少し後で説明します。

このような組み込みの依存関係の解決を使用してコンテキストを変更するにはさまざまな方法があります。MSDN ドキュメント「IDbDependencyResolver サービス」(bit.ly/13Aojso、英語) には、DbConfiguration で使用できるリゾルバーがすべて紹介されています。依存関係の解決は、プロバイダーのコード作成者がコンテキストとプロバイダーの対話にルールやロジックを挿入する必要がある場合に、特定の問題を解決するのにも役立ちます。

クエリとコマンドのインターセプト機能: CustomDbConfiguration の AddInterceptor の使用方法について説明し忘れていました。DbConfiguration を使用すると、IDbDependencyResolver をほとんど強制的に使用することになります。EF6 の新機能には、クエリやコマンドをインターセプトできる機能もあります。クエリやコマンドをインターセプトすると、データベースやこれらのコマンドから返された結果に送信される、生成済みの SQL にアクセスできます。この情報を使用して SQL コマンドをログ記録したり、編集することもできます。また、更新されたコマンドを使用するように EF に指定できます。この機能の使用方法について、まだ楽しく説明していたいところですが、Arthur Vickers が執筆した 3 部構成のブログ シリーズについても忘れずに紹介する必要があります。このブログ シリーズの第 3 部「EF6 SQL Logging – Part 3: Interception building blocks」(EF6 SQL ログの第 3 部: インターセプトのビルド ブロック、bit.ly/19om5du、英語) から、第 1 部 (「Simple Logging」(シンプルなログ記録)) と第 2 部 (「Changing the content/formatting」(コンテンツと書式設定の変更)) も参照することができます。

EF 複数形化のカスタマイズ: EF 複数形化をカスタマイズする機能について説明する前に、この既定の動作について確認します。EF は、次の 3 つのタスクで内部の複数形化サービスを使用します。

  1. Database First では、エンティティに単数形化された名前が付いていることを確認します。そのため、データベース テーブルが People という名前の場合、モデルでは Person となります。
  2. Database First または Model First 用の EF デザイナーでは、EntitySet 名 (DbSet のベース) が Entity 名に基づいて複数形で作成されます。たとえば、サービスでは Person エンティティの EntitySet 名は People になるようにします。Database First を使用してモデルを作成した場合、テーブル名は使用しません。
  3. Code First では DbSet の名前を明示的に付けるため、EF はサービスを使用してテーブル名を推論します。Person という名前のクラスで始まる場合、データベース テーブルの名前は規約から People であると推測されます。

このサービスは、ユーザー設定のスペルの一覧を指定できるスペルの辞書のようには機能しませんが、内部ルールを使用します。時折発生する例外は別にして (以前、面白いエンティティ名が作成されたことがあります)、このサービスの最大の問題はルールが英語に基づいているということです。

EF6 では、Zorrilla が固有のロジックを追加できるように IPluralizationService インターフェイスを作成しました。カスタム サービスを作成したら、既に説明したように DbConfiguration を使用して組み込むことができます。

現在このカスタマイズ機能は、上記の一覧にある、Code First でテーブル名を推論する 3 番目のタスクでのみ機能します。EF6 の初期リリースでは、このようなカスタマイズをデザイナーに適用することはできません。

このサービスを使用する方法は 2 つあります。最初にベース サービス (EF の EnglishPluralizationService または他のユーザーによって既に構築済みのサービス) を用意し、Singularize メソッドまたは Pluralize メソッドをオーバーライドして、固有のルールを追加します。さらに、CustomPluralizationEntry クラスに単語のペアを指定し、そのクラスを既存のサービスに適用することもできます。Zorrilla は自身のブログ記事 (bit.ly/161JrD6、英語) で CustomPluralizationEntry クラスについて説明しています。

今後のデータ ポイントのコラムでは、複数形化のルールの追加例を (単語のペアの追加例以外にも) 紹介します。また、Code First データベース マッピングにおける影響についても説明します。

Code First の優れた機能

Code First 用の新機能は、Code First Migrations など、これまで説明した機能以外にも数多くあります。スペースの都合上、ここでは概要を示し、詳細については 2014 年 1 月のデータ ポイントのコラムで引き続き説明します。

  • どのような移行ポイントからでもデータベースを更新できるように、実行済みの移行を確認できる移行スクリプトを作成する機能。
  • さまざまなデータベース プロバイダーで構成される Migrations_History テーブルの細かい制御。
  • 常に既定で dbo を指定するのではなく、データベース マッピングの既定のスキーマを指定する機能。
  • 同じデータベースを対象とするさまざまな DbContext を処理するための移行機能。
  • 複数の EntityTypeConfiguration を 1 行のコードで 1 つずつ追加するのではなく、すべて一度に追加する ModelBuilder の機能。

上級者向けの機能を使用してみる

個人的見解ですが、EF6 に関して重要なのは、EF の既存の機能に優れた機能が追加されていることです。プロジェクトを EF5 から EF6 に移行する場合、名前空間の変更については注意が必要です。チームは、この移行に関するガイダンスを提供しています。詳細については、bit.ly/17eCB4U(英語) を参照してください。それ以外については、EF6 を問題なく使用できるでしょう。EF6 は Entity Framework の最新の安定したバージョンで、NuGet を通じて配布されています。この上級者向けの機能をすぐに使用する予定がない場合でも、この記事で説明したようなパフォーマンスの向上によるメリットを得られることを覚えておいてください。上級者向けの機能に期待が高まる一方で、EF6 の開発に携わったコミュニティの開発者の皆さんに感謝します。

EF は進化し続けています。EF6 の初期リリースは Visual Studio 2013 のリリースと同時に公開されました。EF 6.1 以降のバージョンも既に開発中です。開発の進捗については、CodePlex サイトで確認してください。

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

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