データ ポイント
Entity Framework に関する Q&A
John Papa
コードのダウンロード : :
DataPoints2008_05.exe
(962 KB)
Browse the Code Online

コンテンツ
この話題について私の所に寄せられた数百の質問から判断すると、開発者たちは、Entity Framework とそのデータ アクセスおよびモデリングの関係にたいへん興味を示しているようです。そのすべてに答えることはできないかもしれませんが、今月のコラムでは、いくつかのよく寄せられる質問に対して回答します。
Entity Framework を理解するための第一歩は、エンティティ モデリング、エンティティ モデルとリレーショナル データベースのマッピング、および Entity Data Model (EDM) のデザインを理解することです。この Q & A では、最初に ObjectContext を含む Entity Framework の基盤に関するいくつかの質問に回答した後、Entity Client と Entity SQL を組み合わせて使うことが適している場面について説明します。さらに、EntityClient と Object Services の違いについて説明し、LINQ および Entity SQL をこれらのサービスと共に使用する意味合いについて説明します。
また、Microsoft® .NET Framework を対象としたコードとネイティブ SQL コードの両方で作成されたクエリの分析も Entity Framework の重要な部分なので、生成された SQL を示しながら明示的な読み込みと一括読み込みについて説明します。サンプル コードとサンプル NorthwindEF データベースは、MSDN® Magazine Web サイトから、このコラムと共にすべてダウンロードできます。
LINQ を使用してエンティティにアクセスできるのになぜ Entity SQL を使用するのですか。 Entity SQL と EntityClient または Object Services の組み合わせを使用するプレゼンテーションを行うたびに、この質問を受けます (私には質問者を責めることはできません。これは、私自身が Entity Framework を使い始めたときに最初に頭に浮かんだいくつかの質問のうちの 1 つだからです)。LINQ の厳密な型指定とクエリ構文は非常に魅力的であり、開発者がエンティティを操作するための新しい言語の必要性に疑問を持つのは当然です。
この質問に対して十分な回答を示すためには、EDM を操作するために使用できる 3 つの主要な手法を最初に説明する必要があります。
- EntityClient プロバイダを使用して Entity SQL クエリを作成する方法
- Object Services を使用して Entity SQL クエリを作成する方法
- Object Services を使用して LINQ クエリを作成する方法
これらの手法の間には、たとえば EntityClient プロバイダを直接的または間接的に使用するという共通の特徴があります。ただし、得られる結果と、その結果を得るための方法は異なります。
EntityClient プロバイダには、ADO.NET オブジェクト モデルの経験がある読者には馴染みのある一連のオブジェクトがあります。EntityConnection を使用して EDM に接続し、EntityCommand を使用して EDM に対してクエリを発行します。コマンドの結果は、DbDataReader を介して返されます。EntityClient を直接的に使用した場合も、または Object Services を介して間接的に使用した場合でも、最終的に EntityClient はクエリを発行して結果を返します。
さて、質問に戻りましょう。LINQ を使用してエンティティにアクセスできるのになぜ Entity SQL を使用するのでしょうか。その答えは、それぞれの手法の長所と短所にあります。
EntityClient + Entity SQL
EntityClient API を使用してコードを記述すると、3 つの手法をきめ細かく制御できます。EDM に接続するための EntityConnection を作成し、Entity SQL でクエリを記述してそれを EntityCommand を使って実行し、その結果を DbDataReader を介して返すことができます。また、この手法は、LINQ や Object Services で提供される構文糖を一部省略することで、わずかながら軽量化されています。
Entity SQL の最大の長所は柔軟性です。文字列ベースの構文は、動的クエリの作成に適しています。アドホック クエリを作成する必要がある場合、これは非常に便利です。
ただし、この柔軟性と軽量化からもたらされる 1 つの影響として、結果を返すために必ず DbDataReader を経由する必要があります。EntityClient と Entity SQL の組み合わせを使用して EDM から純粋なエンティティを返すことはできません。DbDataReader を取得し、これを使用して Entity SQL クエリを満たす行のコレクションを反復処理することができます。図 1 に示すコードでは、Customers エンティティではなく、DbDataReader 経由で顧客レコードに対する反復処理を行っています。

Figure 1 DbDataReader を介した行の反復処理
string city = "London";
using (EntityConnection cn = new EntityConnection("Name=Entities"))
{
cn.Open();
EntityCommand cmd = cn.CreateCommand();
cmd.CommandText = @"SELECT VALUE c FROM Entities.Customers AS c WHERE
c.Address.City = @city";
cmd.Parameters.AddWithValue("city", city);
DbDataReader rdr = cmd.ExecuteReader(CommandBehavior.SequentialAccess);
while (rdr.Read())
Console.WriteLine(rdr["CompanyName"].ToString());
rdr.Close();
}
ここで良いヒントを紹介します。デバッグ モードにおいて、列序数 X から読み取ることができないというエラーが出力される場合があります。このエラーはデバッグ モードでのみ発生し、デバッグ モードの自動ウィンドウを閉じることで回避できます。これは、Entity Framework Beta 3 の既知の問題です。
現在、Entity SQL と同等のデータ操作言語 (DML) は存在しません。つまり、EDM に対して Insert ステートメント、Update ステートメント、または Delete ステートメントを直接表現することはできません (図 2 を参照)。

Figure 2 Entity Framework API
| |
EntityClient と Entity SQL |
Object Services と Entity SQL |
Object Services と LINQ |
| EntityClient プロバイダの直接操作 |
はい |
いいえ |
いいえ |
| アドホックに適している |
はい |
はい |
いいえ |
| DML を直接発行できる |
いいえ |
いいえ |
いいえ |
| 厳密に型指定されている |
いいえ |
いいえ |
はい |
| エンティティを結果として返す |
いいえ |
はい |
はい |
Object Services + Entity SQL
Object Services を使用する 2 番目の手法は、Entity SQL を使用してクエリを実行する方法です。これは、(見えない部分でプロバイダとの通信を行いますが) EntityClient プロバイダとの直接的な対話から離れるやり方です。ObjectContext と ObjectQuery<T> を使用して、EDM に対するクエリを発行します。
この手法は、1 番目の手法と同様に、アドホック クエリを発行するのに適しています。ただし、Object Services と Entity SQL の組み合わせでは、DbDataReader を介してデータを返すのではなく、EDM からエンティティを返すことができます。これにより、安定した組み合わせの、第一級のエンティティを返す柔軟なクエリが実現されます。
現在、Entity SQL には DML 構造がないので、Entity SQL と Object Services を使用して Insert、Update、Delete の各コマンドを発行することはできません。ただし、この手法を使用して EDM からエンティティを取得した後、ObjectContext の SaveChanges メソッドを使用してエンティティを更新することは可能です。見てわかるように、次のコード サンプルでは、Customers のコレクションに対して反復処理を行っています。
string city = "London";
using (Entities entities = new Entities())
{
ObjectQuery<Customers> query = entities.CreateQuery<Customers>(
"SELECT VALUE c FROM Customers AS c WHERE c.Address.City = @city",
new ObjectParameter("city", city)
);
foreach (Customers c in query)
Console.WriteLine(c.CompanyName);
}
Object Services + LINQ
Object Services と LINQ を組み合わせて使用することは、アドホック クエリについては他の手法ほど適切ではありません。次のコード サンプルは、EDM から Customers のコレクションを返します。
string city = "London";
using (Entities entities = new Entities())
{
var query = from c in entities.Customers
where c.Address.City == city
select c;
foreach (Customers c in query)
Console.WriteLine(c.CompanyName);
}
Entity SQL と同様に、LINQ でも DML ステートメントの直接構文がサポートされません。現在、Object Services を使用している場合は、(SaveChanges メソッドを使用して) データベースに対してエンティティを更新することのみが可能です。これは、Entity Framework で変更が追跡されている EDM からエンティティを返すことによって実現されます。つまり、LINQ と Entity SQL のどちらも更新操作を行いません。この操作を行うのは、EDM の ObjectContext です。
これらの手法の相違点を図 2 にまとめました。では、LINQ があるのになぜ Entity SQL を使用するのでしょうか。Entity SQL は、アドホック クエリが必要な場合や、LINQ では不可能なより柔軟なクエリを作成する場合に適した選択肢です。それ以外の場合は、厳密な型指定やエンティティと投影を返す機能のメリットを得られるように、LINQ と Object Services の組み合わせをお勧めします。
LINQ の厳密に型指定された構文に加え、アプリケーションの実行時でなければキャッチできない多くのエラーについてデザイン時のビューを得ることができます。私はこの機能をたいへん気に入っています。エラーをキャッチするためにビルドと実行を繰り返す必要なく、コードの記述に専念できるからです。
ObjectContext はどのような役割を持ちますか。 ObjectContext は、Object Services の EntityConnection のゲートウェイとして機能します。ObjectContext は、基になる EntityConnection を介して EDM へのアクセスを提供します。たとえば、ObjectContext を介してエンティティにアクセスしたり、オブジェクトの状態に関する情報を ObjectContext に問い合わせたりできます。さらに、CreateQuery メソッドを使用して ObjectQuery<T> クエリを作成することもできます。
ObjectContext のもう 1 つの目的は、オブジェクトがデータベース エントリに関する更新情報を取得するための方法を提供することです。たとえば、ObjectContext のメソッドを使用して、ObjectContext へのエンティティの追加、エンティティの削除、エンティティの操作などを行い、最終的に (SaveChanges メソッドを介して) エンティティの変更をデータベースに保存することができます。
Entity Framework で明示的な読み込みと一括読み込みはどのように動作しますか。 明示的な読み込みは、LINQ to Entities および Entity Framework における既定の動作です。Entity Framework 内でクエリが実行されたとき、クエリから返されたエンティティは完全にアクセス可能になりますが、関連付けられているエンティティの読み込みは即座には行われません。たとえば、EDM 内のすべての Orders を取得するクエリを作成した場合、SQL クエリは、注文レコードを取得して Orders エンティティのコレクションを返す処理の内部で実行されます。ただし、注文に関連付けられている顧客レコードはこのクエリの一部としてフェッチされないため、Orders エンティティに関連付けられている Customers エンティティは読み込まれません。したがって、次のコード サンプルの場合、Order の Customers にアクセスしようとしたときに Customers が読み込まれていないために例外をスローします。
using (Entities entities = new Entities())
{
var query = (from o in entities.Orders
where o.Customers.CustomerID == "ALFKI"
select o).First<Orders>();
Orders order = query as Orders;
Console.WriteLine(order.OrderID);
Console.WriteLine(order.Customers.CompanyName);
}
Entity Framework では、EntityReference クラスのそれぞれのインスタンスで Load メソッドが提供されます。このメソッドを使用すると、別のエンティティに関連付けられているコレクションを明示的に読み込むことができます。たとえば、Entity Framework を使って Order に対応する Customers レコードを取得するように前のコード例を変更できます。明示的な読み込みを実行するように書き直したコードを図 3 に示します。このコードでは、最初に Customers エンティティが読み込まれているかどうかをチェックします。読み込まれていない場合は、Order に対応する Customers レコードを読み込みます。この手法は、明示的な読み込みと呼ばれます。

Figure 3 明示的な読み込み
using (Entities entities = new Entities())
{
var query = (from o in entities.Orders
where o.Customers.CustomerID == "ALFKI"
select o);
foreach (Orders order in query)
{
if (!order.CustomersReference.IsLoaded)
order.CustomersReference.Load();
Console.WriteLine(order.OrderID + " --- " +
order.Customers.CompanyName);
}
}
図 3 の例では、SQL クエリを実行して、処理されたそれぞれの注文に対する Customer レコードを取得します。この手法で注文の反復処理を行う場合は、注文が数百でも、または数十であっても、データベースに対して別個のクエリを大量に発行することになります。クエリから返されるデータよりも多くのデータ (たとえば注文に対応する顧客情報) が必要になることが事前にわかっている場合は、この情報を前もって読み込むことができます。
このトピックに関する、Entity Framework を担当する Microsoft チームからのさらなる考察については、囲み記事「インサイト : Entity Framework のデータの読み込み」を参照してください。
図 4 は、一括読み込みと呼ばれる手法を示しています。LINQ クエリの Orders エンティティで呼び出される Include メソッドは、Orders と共に関連する Customers も取得するように指示する 1 つの引数を受け取ります。この方法では、LINQ クエリ内の条件を満たすすべての Orders および Customers を読み込む単独の SQL ステートメントが生成されます。

Figure 4 一括読み込み
using (Entities entities = new Entities())
{
var query = (from o in entities.Orders.Include("Customers")
where o.ShipCountry == "USA"
select o);
foreach (Orders order in query)
Console.WriteLine(order.OrderID + " --- " +
order.Customers.CompanyName);
}
読み込み方式は、十分に検討する必要がある問題です。(図 3 に示すように) Load メソッドを使用して、コレクションに対する反復処理の間にエンティティを明示的に読み込む場合、(Load メソッドの呼び出しごとに実行される) いくつかのクエリがデータベースにアクセスすることになります。
これは、1 回か 2 回データにアクセスするだけで必要な作業を完了できる場合は、まったく申し分のない方法です。ただし、特定のエンティティに対して、関連するエンティティのデータに常にアクセスする必要があることがわかっている場合は、図 4 に示す Include メソッドを使用した一括読み込みの方が優れた選択肢です。
最善の方法は、任意の異なるオプションのパフォーマンスを測定およびテストして、自分のシナリオに最適な方法を見つけることです。ほとんどの決定事項と同様に、一括読み込みと明示的な読み込みのどちらを使用するかは、状況によって異なります。
実行される SQL を確認するにはどうしたらよいですか。 ObjectQuery を使用してクエリを作成したときに、どの SQL が実行されるかを調べたいと思ったことは何回もありました。そして、2 とおりの便利な方法を見つけました。1 つ目は、SQL Server® Profiler ツール (または、使用可能なデータベース エンジンのプロファイリング ツール) を使用する方法です。2 つ目は、ObjectQuery クラスの ToTraceString メソッドを使用する方法です。
図 5 に、ObjectQuery<T> で ToTraceString メソッドを呼び出す方法を示します。ObjectContext の接続が開いていることに注目してください (EntityConnection)。ToTraceString メソッドを実行するためには、実行するクエリを判定できるように接続が開いている必要があります。ToTraceString メソッドは、ObjectQuery<T> と EntityCommand クラスの両方で使用できます。

Figure 5 ToTraceString の使用
string city = "London";
using (Entities entities = new Entities())
{
ObjectQuery<Customers> query = entities.CreateQuery<Customers>(
"SELECT VALUE c FROM Customers AS c WHERE c.City = @city",
new ObjectParameter("city", city)
);
entities.Connection.Open();
Console.WriteLine(query.ToTraceString());
foreach (Customers c in query)
Console.WriteLine(c.CompanyName);
}
複合型を使用すると何ができますか。 Entity Framework は、複合型と呼ばれるデータ構造を提供します。複合型はプロパティのセットを表すもので、通常は互いに密接に関連しています。ここで、Address 型を考えてみましょう。複合型を使用して顧客の Address 型を作成することで、Customer エンティティの住所に関連するプロパティ (City、Region、Phone など) が直接 Customer 型ではなく、Address 型に分類されるようになります。複合型は、類似するスカラ プロパティの論理的なグループを提供します。これにより、エンティティの密接に関係するプロパティを見つけ出し、EDM で論理的にグループ化することが容易になります。エンティティと同様、複合型はスカラ プロパティを持ちます。また、エンティティとは異なりますが、複合型は ID (キー値) を持たず、ObjectContext を介してデータベースで保持することはできません。複合型は、エンティティ自体ではなく、エンティティの一側面です。複合型は、エンティティの論理的に関連するプロパティをグループ化する場合に優れたツールです。
複合型を作成する方法を教えてください。 現在、EDM デザイナは複合型のビジュアルな作成をサポートしていないため、複合型を作成するには、EDMX ファイルを XML エディタで開く作業が必要になります。最初の手順では、概念スキーマ定義言語 (CSDL : Conceptual Schema Definition Language) を使用して ComplexType を作成し、複合型を参照する EntityType を変更します。図 6 は、新しく作成された AddressType 複合型を含む、CSDL で記述された Customers EntityType を示しています。Customers EntityType にあった Address、City、Region、およびその他の住所に関連するプロパティが削除され、Address という新しいプロパティで置き換えられていることに注目してください。この新しい Address プロパティは、AddressType 複合型を参照します。

Figure 6 ComplexType の作成
<EntityType Name="Customers">
<Key>
<PropertyRef Name="CustomerID" />
</Key>
<Property Name="CustomerID" Type="String" Nullable="false"
MaxLength="5" FixedLength="true" />
<Property Name="CompanyName" Type="String" Nullable="false"
MaxLength="40" />
<Property Name="ContactName" Type="String" MaxLength="30" />
<Property Name="ContactTitle" Type="String" MaxLength="30" />
<Property Name="Address" Type="Self.AddressType"
Nullable="false"/>
<NavigationProperty Name="Orders"
Relationship="NorthwindEFModel.FK_Orders_Customers"
FromRole="Customers" ToRole="Orders" />
</EntityType>
<ComplexType Name="AddressType">
<Property Name="Address" Type="String" MaxLength="60" />
<Property Name="City" Type="String" MaxLength="15" />
<Property Name="Region" Type="String" MaxLength="15" />
<Property Name="PostalCode" Type="String" MaxLength="10" />
<Property Name="Country" Type="String" MaxLength="15" />
<Property Name="Phone" Type="String" MaxLength="24" />
<Property Name="Fax" Type="String" MaxLength="24" />
</ComplexType>
次の手順では、マッピング仕様言語 (MSL : Mapping Specification Language) を使用して、新しい複合型を反映するようにマッピングを変更します。図 7 は、新しい AddressType 複合型を含む、Customers EntityType のマッピングを示しています。プロジェクトをビルドした後はコードで複合型を参照できます。たとえば、次の LINQ クエリを実行すると、London に居住する顧客のみをフィルタ選択できます。

Figure 7 複合型のマッピング
<EntitySetMapping Name="Customers">
<EntityTypeMapping
TypeName="IsTypeOf(NorthwindEFModel.Customers)">
<MappingFragment StoreEntitySet="Customers">
<ScalarProperty Name="CustomerID" ColumnName="CustomerID" />
<ScalarProperty Name="CompanyName" ColumnName="CompanyName" />
<ScalarProperty Name="ContactName" ColumnName="ContactName" />
<ScalarProperty Name="ContactTitle" ColumnName="ContactTitle" />
<ComplexProperty Name="Address"
TypeName="NorthwindEFModel.AddressType">
<ScalarProperty Name="Address" ColumnName="Address" />
<ScalarProperty Name="City" ColumnName="City" />
<ScalarProperty Name="Region" ColumnName="Region" />
<ScalarProperty Name="PostalCode" ColumnName="PostalCode" />
<ScalarProperty Name="Country" ColumnName="Country" />
<ScalarProperty Name="Phone" ColumnName="Phone" />
<ScalarProperty Name="Fax" ColumnName="Fax" />
</ComplexProperty>
</MappingFragment>
</EntityTypeMapping>
</EntitySetMapping>
var query = from c in entities.Customers
where c.Address.City == "London"
select c;
まとめ
今月のコラムでは、EntityClient と Object Services、Entity SQL、LINQ の使い方を比較しました。さらに、複合型を作成する方法とその理由について触れ、一括読み込みと明示的な読み込みの動作を紹介しました。Entity Framework については、意欲的な開発者の皆さんからたくさんの質問が寄せられているので、将来の「データ ポイント」で同じようなトピックや実用的なヒントを紹介する予定です。
ご意見やご質問は、John まで英語でお送りください。 mmdata@microsoft.com.
John Papa は、ASPSOFT (
aspsoft.com) で働く上級 .NET コンサルタントです。野球を熱狂的に愛し、夏の夜のほとんどを家族および忠犬 Kadi (カーディ) と共にヤンキースのために費やします。C# MVP であり、INETA の講演者でもある John は、ADO、XML、および SQL Server に関して何冊かの書籍を執筆しています。また、主に業界のカンファレンス (DevConnections、VSLive など) での講演やブログ (
johnpapa.net) などで活躍しています。