データ ポイント

Entity Framework のプレビュー版: コード ファースト、ObjectSet クラス、および DbContext クラス

Julie Lerman

Entity Framework (EF) 4 では、ベータ版の段階から、開発チームは、EF を使用する別の方法への取り組みを開始しました。従来、モデルを作成する方法としては、データベース優先の方法 (データベースから Entity Data Model へのリバース エンジニアリング) と、EF 4 の新しいモデル優先の機能 (Entity Data Model を定義して、そこからデータベースを作成する方法) がありました。この 2 つの方法に加えて、開発チームでは "コード ファースト" と呼ばれる方法を開発することにしました。

コード ファーストの方法では、データベースやモデルではなく、まず初めにコードを記述します。実際、コード ファーストでは、視覚的なモデルや、そのモデルを定義する XML が存在しません。アプリケーション ドメインのクラスを作成すれば、コード ファーストの方法で作成したクラスが EF で使用できるようになります。コンテキストを使用し、LINQ to Entities クエリを記述して実行し、EF の変更追跡機能を活用できます。しかし、デザイナーを使用して、モデルを操作する必要はありません。複雑な設計のアプリケーションを構築しているわけでなければ、EF が存在していて、すべてのデータベース操作と変更追跡を処理していることを忘れてしまいそうになるくらいです。

おそらく、ドメイン駆動設計 (DDD) のファンは、コード ファーストを気に入るでしょう。実際、コード ファーストで DDD のプログラマが実行できる処理について、EF チームが理解できるように手助けしたのは、数人の DDD の第一人者たちでした。詳細については、Danny Simmons の Data Programmability Advisory Council に関するブログ投稿 (blogs.msdn.com/b/dsimmons/archive/2008/06/03/dp-advisory-council.aspx、英語) を参照してください。

ですが、コード ファーストは Microsoft .NET Framework 4 と Visual Studio 2010 に組み込む準備は整っていませんでした。事実、コード ファーストは今も開発中で、マイクロソフトは、Entity Framework Feature CTP (EF Feature CTP) を提供して、開発者がコード ファーストを試して、フィードバックを提供できるようにしています。Community Technology Preview 4 (CTP4) は、2010 年 7 月半ばにリリースされました。このバージョンでは、重要な変更が加えられました。

CTP4 では、コード ファーストのアセンブリが追加されていますが、マイクロソフトでは、EF の次の CTP に向けて優れた新しいアイデアへの取り組みが行われています。このアイデアは CTP4 でも採用されているため、開発者は、新機能を試して、フィードバックを提供することができます。さらに重要なことに、コード ファーストでは、この新機能のいくつかを活用しています。

この CTP に関するいくつかの詳細なチュートリアルについては、EF チーム (tinyurl.com/297xm3j、英語)、Scott Guthrie (tinyurl.com/29r3qkb、英語)、および Scott Hanselman (tinyurl.com/253dl2g英語) のブログ投稿を参照してください。

このコラムでは、CTP4 のいくつかの機能と機能強化に的を絞って紹介します。これらの機能は EF (および .NET Framework 全般) を使用した開発を大幅に簡素化するので、EF に関する大旋風を巻き起こしました。

コア API の強化

CTP4 で EF のランタイムに追加された特筆すべき機能強化の 1 つは、2 つの主要なクラスである ObjectContext と ObjectSet の軽量バージョンです。ObjectContext クラスでは、クエリ、変更追跡、およびデータベースへのデータの格納が行えるようになります。ObjectSet クラスでは、一連のオブジェクトのようなものをカプセル化できます。

新しいクラスである DbContext と DbSet には、同じ重要な機能が備わっていますが、ObjectContext クラスと ObjectSet クラスの完全な機能セットは公開されていません。すべてのコーディングで、より堅牢なクラスのすべての機能にアクセスできる必要があるわけではありません。DbContext クラスと DbSet クラスは ObjectContext クラスと ObjectSet クラスから派生したものではありませんが、DbContext クラスでは、DbContext.ObjectContext プロパティを通じて完全な機能を備えたバージョンに簡単にアクセスできます。

ObjectSet クラスの基礎と簡素化された DbSet クラス

ObjectSet クラスは、一連のエンティティ型を表す汎用的なコレクションのようなものです。たとえば、Customers という ObjectSet<Customer> を使用できます (これはコンテキスト クラスのプロパティです)。LINQ to Entities クエリは、複数の ObjectSet に対して記述します。Customers ObjectSet に対するクエリを次に示します。

from customer in context.Customers 
where customer.LastName=="Lerman" 
select customer

ObjectSet クラスには内部コンストラクターがありますが、パラメーターなしのコンストラクターはありません。ObjectSet クラスを作成するには、ObjectContext.CreateObjectSet メソッドを使用します。一般的なコンテキスト クラスでは、Customers プロパティは次のように定義されています。

public ObjectSet< Customer> Customers {
  get { 
    return _ customers?? (_customers = 
      CreateObjectSet< Customer >(" Customers"));
  }
}

private ObjectSet< Customer > _ customers;

これで、Customers プロパティに対するクエリを作成し、必要に応じて、オブジェクトの追加、アタッチ、および削除を行うことで、返された一連のプロパティの値を操作できます。

DbSet クラスの使い方は、はるかに単純です。DbSet クラスは、(ObjectSet クラスと同様に) パブリックに構築できません。ですが、複数の DbSet が自動的に作成され、これらは (パブリックな setter を使用して) 派生した DbContext クラスで宣言したプロパティに割り当てられます。

ObjectSet クラスのメソッドでは、EF の用語に合わせた名前 (AddObject、Attach、および DeleteObject) が使用されています。

context.Customers.AddObject(myCust)

DbSet クラスでは、単純に Add、Attach、および Remove という名前のメソッドを使用しています。これは、その他のコレクションのメソッド名に近いので、EF の用語の使用方法などを把握するために時間を費やす必要はありません。

context.Customers.Add(MyCust)

DeleteObject ではなく Remove という名前を使用することで、このメソッドで実行される処理がわかりやすくなります。Remove メソッドでは、コレクションからアイテムを削除します。DeleteObject という名前からは、データベースからデータが削除されることが連想されるため、多くの開発者が混乱する原因となっていました。

私が断然気に入っている DbSet クラスの機能は、派生型をより簡単に使用できるところです。ObjectSet クラスでは、基本型と基本型から派生するすべての型をラップします。基本エンティティ (Contact と、ここから派生する Customer エンティティ) がある場合は、Customer を操作する必要があるたびに、Contact ObjectSet から開始しなければならなくなります。たとえば、Customer をクエリする場合は、context.Contacts.OfType<Customer> と記述します。これは紛らわしいだけでなく、簡単には検出できません。DbSet クラスでは、派生型をラップできるので、DbSet<Customer> を返す Customers プロパティを作成して、一連の Contact を通じてではなく、プロパティを直接操作できます。

DbContext クラス (合理化されたコンテキスト)

DbContext クラスでは、ObjectContext クラスの最もよく使用される機能が公開され、いくつかの非常に便利な追加機能が提供されます。現時点で、私が気に入っている DbContext クラスの機能は、汎用的な Set プロパティと OnModelCreating メソッドです。

ここまでに言及してきたすべての ObjectSet は、ObjectContext インスタンスの明示的なプロパティです。たとえば、3 つのエンティティ (Patient、Address、および OfficeVisit) を含む PatientHistory というモデルがあるとします。ObjectContext クラスを継承している PatientHistoryEntities というクラスがあります。このクラスには、ObjectSet<Patient> という Patients プロパティと、Addresses プロパティおよび OfficeVisits プロパティが含まれています。ジェネリックを使用して動的なコードを作成する場合は、context.CreateObjectSet<T> を呼び出す必要があります (この T には、エンティティ型を指定します)。ただし、この場合も、これでは検出できません。

DbContext クラスには、Set と呼ばれるより単純なメソッドがあります。このメソッドを使用すると、context.Set<T> を呼び出すことが可能で、DbSet<T> が返されます。これは 12 文字ばかり文字数が少ないように見えるだけかもしれませんが、コーディングに関する私の考えでは、プロパティを使用するのは正しいと感じるのに対して、ファクトリ メソッドを呼び出すのは適切に思えません。また、このプロパティでは、派生エンティティを使用することもできます。

もう 1 つの DbContext クラスのメンバーは、OnModelCreating メソッドです。このメソッドは、コード ファーストで役に立ちます。ドメイン モデルに適用する必要がある他の構成は、EF がドメイン クラスに基づいてメモリ内メタデータをビルドする直前に、OnModelCreating メソッドで定義できます。これは、これまでのバージョンのコード ファーストからの大きな改善点です。詳細については、これ以降で説明します。

コード ファーストの高性能化と単純化

コード ファーストは、2009 年 1 月に、EF Feature CTP1 で、"Code Only (コードのみ)" という名前で初めて開発者に公開されました。EF を使用する別の方法への取り組みが行われた背景には、単純にドメイン クラスを定義して、物理的なモデルの定義に時間をかけたくないという開発者の希望がありました。ただし、EF のランタイムは、モデルに対するクエリをデータベース クエリに強制的に変換して、データベースから返されるクエリ結果をモデルで定義されているオブジェクトに強制的に変換するために、モデルの XML に依存しています。そのメタデータがなければ、EF では処理を実行できません。しかし、メタデータは物理ファイルに格納されている必要はありません。EF では、アプリケーション プロセスで XML ファイルを一度読み取り、その XML に基づいて厳密に型指定されたメタデータ オブジェクトを作成します。その後の操作は、すべてメモリ内 XML を使用して実行します。

コード ファーストでも、メモリ内メタデータ オブジェクトが作成されます。しかし、コード ファーストでは、XML ファイルを読み取ってメタデータ オブジェクトを作成するのではなく、ドメイン クラスからメタデータを推測します (図 1 参照)。コード ファーストでは、規約を使用して推測が行われ、その他の構成を追加してモデルを改良できる方法が提供されます。

image: Code First Builds the Entity Data Model Metadata at Run Time

図 1 コード ファーストによる、実行時の Entity Data Model メタデータのビルド

コード ファーストのもう 1 つの重要な機能は、メタデータを使用して、データベース スキーマとデータベース自体も作成できることです。この機能は、コード ファーストが初めて公開されたときから提供されています。

ここで、いくつかの無効な推測を切り抜けるために構成が必要な例を紹介します。ConferenceTrack というクラスと TrackId という ID プロパティがあるとします。コード ファーストの規約では、Id または "クラス名 + Id" という ID プロパティに相当する文字列を探して、エンティティの EntityKey およびデータベース テーブルの主キーに使用します。しかし、TrackId プロパティは、このパターンに当てはまらないので、これが ID キーであることを EF に指示する必要があります。

コード ファーストの新しい ModelBuilder クラスでは、前述のクラスに基づいてメモリ内モデルがビルドされます。また、ModelBuilder クラスを使用して構成を定義することもできます。次の ModelBuilder クラスの構成を使用して、ConferenceTrack エンティティが TrackId プロパティをキーとして使用するよう指定できます。

modelBuilder.Entity<ConferenceTrack>().HasKey(
  ct => ct.TrackId);

ModelBuilder クラスでは、メモリ内モデルを作成してデータベース スキーマを分析するときに、この追加情報を考慮するようになります。

構成をより論理的に適用する

ここで DbContext.OnModelCreating メソッドが非常に役立ちます。このメソッドに構成を配置すると、DbContext クラスでモデルが作成されるときに構成を適用するようにできます。

protected override void OnModelCreating(
  ModelBuilder modelBuilder) { 
  modelBuilder.Entity<ConferenceTrack>().HasKey(
    ct => ct.TrackId); 
}

CTP4 に追加されたもう 1 つの新機能は、クラスの属性を通じて構成を適用する方法です。この技法は、"データ注釈" と呼ばれます。ConferenceTrack クラスの TrackId プロパティに Key 注釈を直接適用することで、同じ構成を設定できます。

[Key] 
public int TrackId { get; set; }

ただし、プログラムによる構成を使用すると、クラスに EF 固有の情報が含まれていなくても問題がなく、この方が単純なので、個人的には、この方法が好きです。

この手法を使用すると、DbContext クラスでモデルのキャッシュが処理されるので、さらに DbContext クラスを構成しても、再びモデルの検出コストが発生することはありません。

リレーションシップの単純化

コード ファーストの最も注目すべき機能強化の 1 つは、クラスからの推測の精度がはるかに高くなったことです。コード ファーストの規約には多くの改良がなされていますが、私のコードで最も影響があると感じたのは、強化されたリレーションシップの規約でした。

クラスでリレーションシップが定義されても、以前の CTP では、構成情報を提供してリレーションシップをモデルで定義する必要がありました。また、この構成は優れているわけでも論理的なわけでもありませんでした。コード ファーストでは、クラスで定義されたほとんどのリレーションシップの目的を正しく解釈できるようになりました。また、いくらかの構成を使用してモデルを調整する必要がある場合に使用する構文は、はるかに単純になりました。

私のドメイン クラスには、プロパティを通じて定義したいくつかのリレーションシップがあります。たとえば、ConferenceTrack クラスには、一対多のリレーションシップがあります。

public ICollection<Session> Sessions { get; set; }

Session には、逆のリレーションシップと多対多のリレーションシップがあります。

public ConferenceTrack ConferenceTrack { get; set; }
public ICollection<Speaker> Speakers { get; set; }

私が作成したクラスと (TrackId プロパティを会議のキーとして定義した) 1 つの構成を使用すると、図 2 に示すすべてのリレーションシップと継承が適用されたデータベースが、モデル ビルダーによって作成されました。

image: Model Building-Created Database Structure

図 2 モデルのビルドによって作成されたデータベースの構造

多対多のリレーションシップに対応するために Sessions_Speakers テーブルが作成されています。私のドメインには、Session から継承している Workshop クラスがあります。既定では、コード ファーストは、Table-Per-Hierarchy 継承を前提としているので、Sessions テーブルに discriminator 列が作成されました。構成を使用して、この列の名前を IsWorkshop に変更したり、Table-Per-Type 継承を作成するように指定することもできます。

新機能に対応する計画を前もって進める

このコラムで紹介した CTP4 で早期にアクセスできる魅力的な新機能は、今その新機能を必要としている開発者にとってはストレスの元となるかもしれません。確かに、これらの新機能はプレビュー版の機能に過ぎないので、現時点で運用することはできませんが、可能な場合は、新機能を少し試して、フィードバックを EF チームに送ることで、さらに優れた機能にする手助けとなることは間違いありません。また、今後開発するアプリケーションで、コード ファーストと他の EF の新機能をどのように使用するかについて、前もって計画を進めることはできます。

Julie Lerman は、バーモント ヒルズ在住の Microsoft MVP、.NET の指導者、およびコンサルタントです。世界中のユーザー グループやカンファレンスで、データ アクセスなどの Microsoft .NET トピックについてプレゼンテーションを行っています。彼女のブログは thedatafarm.com/blog (英語) で、彼女が執筆した『Programming Entity Framework』(O'Reilly Media、2009 年) は絶賛を浴びました。彼女には Twitter.com (julielermanvt、英語) から連絡できます。

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