January 2017

Volume 32 Number 1

データ ポイント - EF Core 1.1: お気に入りをいくつか

Julie Lerman

Julie Lerman本稿執筆時点 (2016 年 11 月) は、Entity Framework (EF) Core 1.1 がリリースされた直後です。1.0 から 1.1 のリリースの間に、いくつか重要なことが起こりました。特に、修正プログラム 1.0.1 では、1.0 のリリース直後にみつかった重要なバグがいくつか解決されました。修正プログラムの詳細な一覧については、リリース ノート (bit.ly/2fl5xNE、英語) をご覧ください。この修正プログラムのほとんどは、LINQ クエリが SQL に変換されるしくみに関連しています。ただし、モデル ビルダー、SaveChanges による永続化、移行に関する修正プログラムも含まれています。

EF Core 1.1 では、追加のバグ修正、LINQ クエリの解釈方法に対する多くの機能強化、パフォーマンスの大幅な向上が行われています。もちろん、新しい機能もあります。その一部は、EF6 に存在していても EF Core 1.0 には含まれなくなった機能です。この機能廃止により、多くの開発者が EF Core を避けるようになりました。その他は、まったく新しい機能です。今回は、これらの変更点を簡単に一覧してから、筆者が大きな関心を寄せている機能をいくつか見ていきます。また、有益なさまざまなリソースへのリンクも紹介します。これらのリソースを利用して、EF Core の進化に遅れを取らないようにしてください。基本的に、このような情報の多くは、Docs (docs.microsoft.com/ef、英語) やチーム ブログの投稿 (bit.ly/2ehZBUB、英語) で発信されています。

特に、Arthur Vickers のブログをぜひチェックしてください。Arthur は、EF で POCO サポートが開始された頃からチームに所属している上級開発者で、EF の近代化に貢献しています。EF Core 1.1 についても、一連のすばらしい投稿を行っています (blog.oneunicorn.com、英語)。

マイクロソフトは、EF6 の多くの機能を EF Core に持ち込むことを計画していますが、すべてではありません。また、将来のバージョンに含める多くの新機能も計画しています。目下のところ、EF Core 1.1 は、多くの開発者にとって EF Core に対する満足感が高まるようなアップグレードに重点が置かれています。今回は、このような機能の中から最も重要な機能を紹介します。

EF 1.1 に搭載される EF6 の機能

EF 4.1 で DbContext API の導入によって利用可能になった DbSet.Find メソッドは、EF Core の初回イテレーションで廃止リストに載ったことで、多くの開発者を動揺させました。Find は、エンティティのキー値に基づいて効率的にエンティティにクエリを行う便利な方法というだけではありません。そのエンティティがメモリ内に既に存在し、EF によって追跡されているかどうかを確認するチェックを最初に行います。エンティティが DbContext キャッシュに見つからなかった場合、EF はエンティティを取得するためにデータベースに対して FirstOrDefault クエリを実行します。EF6 以前のバージョンでは、SingleOrDefault クエリを使用していました。データベースとの不要なやり取りを避ける Find の設計は、パフォーマンス上のメリットもあります。詳細については、GitHub (bit.ly/2e9V0Uu、英語) で EntityFinder クラスのコードを検索してください。

他の大きな違いは、Find が EntityFinder サービスになったことです。EF6 当時のように内部 DbSet クラスに直接実装されるメソッドではなくなりました。EF Core は、特定のタスクをカプセル化する多数のサービスから構成されています。EF Core 1.1 の Channel 9 ビデオのデモ (bit.ly/2fHOCsN、英語) で、Rowan Miller がサービスを直接使用する方法やサービスを置き換える方法を示しています。

また、「接続の復元性」と呼ばれていた EF6 の機能が、1.1 の更新プログラムで EF Core の一部になります。この機能は、Azure SQL Database などのリモート データベースに対して操作を行う際に発生する可能性がある一時的な接続の問題に簡単に対処するためのサポートを提供します。ASP.NET Core の依存関係の挿入を使用している場合、SQL Database プロバイダーの EnableRetryOnFailure 拡張メソッドは、Startup.cs での DbContext.OnConfiguring 呼び出しまたは AddDbcontext 呼び出しで設定できます。EnableRetryOnFailure は SqlServerRetryingExecutionStrategy を呼び出します。これは、ExecutionStrategy 型から継承されます。開発者は、独自の ExcecutionStrategy を作成して設定できます。また、他のプロバイダーは、独自の ExecutionStrategy の構成を事前に定義することができます。この機能について馴染みのない方は、Pluralsight コースの「EF6 Ninja Edition」(bit.ly/PS_EF6、英語) で、これとよく似た EF6 の DbExecutionStrategy を確認してください。

EF ではこれまで、関連データの読み込み方法を 3 種類提供してきました。1 つは一括読み込みです。この方法は DbSet.Include メソッドによって実現され、単一クエリでデータのグラフを取得します。対照的に、他の 2 つの方法では、メインのオブジェクトを最初にメモリ内に取り込んでから、それらのオブジェクトを追跡する DbContext がまだスコープ内にある間に、関連データを読み込みます。遅延読み込みは、要求時に関連データを自動的に取り込みます。これに対して、明示的読み込みは、EF に明示的に指示することで関連データを読み込みます。これらの読み込みの中で EF Core に最初に実装されたのが Include で、EF Core 1.0 で利用可能になります。EF6 以前のバージョンと比べれば、いくつか機能強化が行われています。Load メソッドによる明示的読み込みは、今回の EF Core 1.1 で追加されています。遅延読み込みはまだサポートされていませんが、いずれサポートされる見込みです。

DbContext の変更追跡 API により、変更追跡情報に直接アクセスできるようになります。たとえば、DbContext.Entry(someObject).State メソッドを使用してエンティティの状態を取得または設定します。EF6 では、GetDatabaseValues、CurrentValues、OriginalValues のような新しいメソッドで追加の制御を行っていました。これらのメソッドも、EF Core 1.1 で利用可能になります。

EF 1.1 の新機能

EF Core には、以前のバージョンにはなかった機能も含まれています。たとえば、SaveChanges 中のバッチ処理、一意外部キー、テスト用の優れた InMemory プロバイダー、スマートな LINQ クエリ処理、スマートかつシンプルな fluent マッピングといった機能です。

EF 1.1 では、いくつか新機能が追加されていますが、ドメイン駆動型設計 (DDD) の熱心な支持者として、特に気に入っている機能が 1 つあります。それは、カプセル化されたコレクションのサポートです。

マップされたフィールドとカプセル化されたコレクション: EF Code First では、getter と setter の両方を備えたプロパティへのマッピングしかサポートされません。setter がプライベートの場合でも同じです。また、コレクションのナビゲーションの場合、プロパティを ICollection にする必要がありました。プロパティ値の設定方法に制約を設ける場合、プロパティをカプセル化する機能が不可欠です。これにより、そのクラスの使用者にパブリック メソッドを強制的に使用させ、カプセル化したプロパティに関するビジネス ルールに確実に従わせることができます。EF6 以前のバージョンでは、setter をプライベートにすれば、スカラーのプロパティをカプセル化することができました。しかし、本当の意味でコレクションをカプセル化して、コレクションを直接修正できないようにする方法はありませんでした。

EF 1.1 では、フィールドに直接マップすることも、IEnumerable プロパティにマップすることもできるようになります。プロパティだけでなく、フィールドにマップする新しい機能によって、setter を隠ぺいするよりも直接的なアプローチを利用できます。また、スカラー値をカプセル化する追加の方法もサポートされます。以下のプロパティ DueDate は、getter を備えていますが setter はありません。また、プロパティに関連付けられた _dueDate というフィールドがあります。

private DateTime _dueDate;
public DateTime DueDate {
  get { return _dueDate;
  }
}

この DueDate を設定する唯一の方法は、CalculateDueDate メソッドを呼び出すことです。このメソッドがフィールドを変更します。

private void CalculateDueDate() {
  _dueDate=Start.AddDays(DaysLoaned);
}

EF Core 1.1 では、クエリの結果を返す場合など、_dueDate フィールドを使用してデータベースにマップできることを EF に通知するには、DbContext で明示的なマッピングが必要です。_dueDate フィールドが DueDate プロパティの代わりになることを指定するには、Property (および、必要に応じて HasField) API メソッドを使用する必要があります。この場合、フィールド名 _dueDate が EF の表記に従っているため、HasField メソッドを使用する必要はありませんが、念のためこれも追加しました。

protected override void OnModelCreating(ModelBuilder modelBuilder) {
  modelBuilder.Entity<BookLoan>().Property(bl => bl.DueDate)
  .HasField(“_dueDate”);
}

フィールド マッピングが利用可能になる前にプライベート setter を使用していた頃は、Add メソッドや Remove メソッドを使ってコレクションを直接操作できないように、コレクションをカプセル化する方法はありませんでした。setter を隠ぺいするのではありません、必要なのは、コレクションのメソッドを隠ぺいすることです。DDD での一般的なアプローチでは、プロパティを IEnumerable にします。ただし、EF6 以前でも可能だったのは ICollection の類にマップすることだけでした。しかし、IEnumerable は ICollection ではありません。

当初、フィールド マッピング機能を使用できるかどうかに注目していましたが、スカラーのプロパティへのマッピングしかサポートされないため、EF 1.1 でも無理だろうと思っていました。EF Core の次期リリースでは、ナビゲーション プロパティにマッピングできるように変更することを目指していました。しかし、ある日、Twitter 上でこのことを指摘したところ、Arthur Vickers が、実際には IEnumerables にマップできることを教えてくれました。これは、EF Core について見逃していた点です。これで、コレクションをカプセル化して完全に保護し、コレクションを変更する際は 1 つのメソッドを必ず使うよう API のユーザーに強制することができます。以下に、融資が行われるたびに新しい BookLoan インスタンスを、ローン期間を必ず含めて帳簿に追加する例を示します。

private List<BookLoan> _bookLoans;
public IEnumerable<BookLoan> BookLoans {
  get { return _bookLoans; }
}
public void BookWasLoaned(int daysLoaned){
  _bookLoans.Add(new BookLoan(DateTime.Today, 14));
}

これは、IEnumerable マッピングを利用してカプセル化を実現する 1 つのパターンです。ですが、Arthur のブログ投稿 (bit.ly/2fVzIfN、英語) で詳細を確認することをお勧めします。このブログでは、バッキング フィールド (上記の _bookLoans フィールドなど) を使用せずに実現する方法や、機能拡張の計画、注目すべき知識などを確認できます。

データベース固有の機能のサポート: EF Core は、プロバイダーがデータ ストアの具体的な機能を簡単にサポートできるよう設計されています。たとえば、EF Core の SQL Server プロバイダーでは、非常に高いスループットを備えたアプリケーションに対して、SQL Server メモリ最適化テーブルをサポートします。この特殊な種類のテーブルに対するエンティティ マップを指定するには、Fluent API を使ってモデルを構成するときに使用可能な拡張メソッドを SQL Server プロバイダーで用意します。

modelBuilder.Entity<Book>().ForSqlServerIsMemoryOptimized();

このことは、コードが最初にテーブル作成スクリプトを生成する方法に影響するだけでなく、EF がデータベースにデータをプッシュするコマンドを生成する方法にも影響します。この興味深いデモは、Rowan Miller が EF 1.1 の機能をデモする前述の Cannel 9 ビデオで確認できます。

EF Core の PostgreSQL プロバイダーをビルドした Shay Rojansky は、配列型などの PosgreSQL の特殊な機能を EF Core でサポートできるようにする方法についての記事を執筆しています。この記事については、こちら (bit.ly/2focZKQ、英語) を確認してください。

サービスへの容易なアクセス

前半の EntityFinder サービスで示したように、EF Core は多くのサービスから構成されています。Channel 9 ビデオでは、Rowan がコードから直接サービスにアクセスして、使用する方法をデモしています。また、独自にカスタマイズしたコードでサービスをオーバーライドすることも可能です。EF 1.1 では、OnConfiguring で使用できるシンプルな置換メソッドを使って、この作業をより簡単に行えるようになります。あるサービスから継承する別のサービスや、同じインターフェイスを実装するサービスに、サービスを置き換えることができます。サービスの特定のリストはありません。サービスは、EntityFrameworkCore のさまざまな API を介したクラスにすぎません。わかりやすい例として、Sqlite プロバイダーからの SqlLiteTypeMapper など、データベースの型マッパーから継承したクラスを作成して、新しい型マッピングルールで追加します。ルールでは、データベースをキャストできるようにします (優れた型変換については、今後のバージョンの EF Core で対応予定です)。 次に、OnConfiguring で、以下のように置換ルールをセットアップします。

optionsBuilder.ReplaceService<SqliteTypeMapper,MySqliteTypeMapper>();

EntityFinder サービスを置き換えないのはなぜでしょう。 まず、この方法がお気に入りだからです。しかし、もう 1 つ理由があります。それはこれがジェネリック クラスであるためです。新しいバージョンのサービスを作成すると複雑になってしまうので、差し当たりこの作業を保留にするためです。ReplaceService は内部サービスの置き換えを簡単にする目的で作成されましたが、EF Core 内部が変化する恐れがあるり、そのため、今後、置換に問題が生じやすくなることを常に念頭に置く必要があります。これらは、Internal で終わる名前空間に含まれているので、すぐにわかります。マイクロソフトによれば、 「マイナー リリースやパッチ リリースでは、通常、.Internal 以外の名前空間には大きな変更を行わないようにしている」とのことです。

注目しておきたいのは (当時、ちょっとした騒ぎになった) Include メソッドを使用した非同期クエリ関連のパフォーマンスの問題に対する修正プログラムです。この問題は、1.0 のリリース直後に見つかりました。この問題は迅速に解決され、70% のパフォーマンス向上が報告されています。興味があれば、bit.ly/2faItD1 (英語) で詳細をご覧ください。この修正は、1.1 リリースにも含まれています。

まとめ

EF Core は、洗練された機能を多数備えています。EF Core に含まれる機能、含まれない機能、対応予定の機能、対応予定のない機能を知っておくことは、とても重要です。EF 6 との関係について記載した EF Core ドキュメントも便利なリソースです (bit.ly/2fxbVVj、英語)。ポイントは、開発者とそのアプリにとって EF Core が適切になるタイミングを判断することです。

EF Core 1.1 は、ほとんどの作業に必要な API や機能を備えています。DDD の実践者としては、コレクションをカプセル化する機能は大いに利用できます。ただし、モデル内で値オブジェクトも使用できるよう、複雑な型マッピングが組み込まれることを待ち望んでいます。この機能は、EF Core の次期リリースで対応される予定です。また、EF Core で待望の読み取り専用 (非追跡) の DbContext を定義する機能が実現されたことに最近気付いて、興奮しています。この機能が早い段階で追加され、EF Core の 1.0 リリースに含まれていたことを完全に見落としていました。詳しくは、筆者のブログ投稿で確認してください (bit.ly/2f75l8m、英語)。


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

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


この記事について MSDN マガジン フォーラムで議論する