2015年10月13日

EntityFramework(9):関連エンティティの一括読み込み (Eagerly Loading)

  
前回に引き続き、関連エンティティの読み込みについて見ていきます。

Productを読み込んだ時には、関連するCategory も必ず読み込むんだから、 同時に読み込んだほうが効率がいい、という場合もあると思います。
そんなときは、Ibclude メソッドを使います。 前回のコードを書き換えてみます。

書き換え前
 using (var db = new NorthwindContext()) {
     db.SetLogging();
     var query = db.Products
                   .Where(p => p.UnitPrice < 10);
     var product = query.First();
     Console.WriteLine(product.Category.CategoryName);
 }

書き換え後
 using (var db = new NorthwindContext()) {
     db.SetLogging();
     var query = db.Products
                   .Where(p => p.UnitPrice < 10);
     var product = query.Include(nameof(Category)).First();
     Console.WriteLine(product.Category.CategoryName);
 }

includeの引数には、一緒に読み込みたいテーブル名を指定します。
nameof演算子は、C#6.0で追加された演算子です。 Include("Category") と書いても同じ結果になります。

なお、もう一つのオーバーロードメソッドを使って、以下のように書くこともできます。

  product = query.Include(p => p.Category).First();

VS2013以前での開発では、こちらの書き方ほうがインテリセンスが効くので楽かもしれませんね。

発行されるSQLは以下の通りです。

書き換え前
 SELECT TOP (1)
    [Extent1].[ProductID] AS [ProductID],
    [Extent1].[ProductName] AS [ProductName],
    [Extent1].[SupplierID] AS [SupplierID],
    [Extent1].[CategoryID] AS [CategoryID],
    [Extent1].[QuantityPerUnit] AS [QuantityPerUnit],
    [Extent1].[UnitPrice] AS [UnitPrice],
    [Extent1].[UnitsInStock] AS [UnitsInStock],
    [Extent1].[UnitsOnOrder] AS [UnitsOnOrder],
    [Extent1].[ReorderLevel] AS [ReorderLevel],
    [Extent1].[Discontinued] AS [Discontinued]
    FROM [dbo].[Products] AS [Extent1]
    WHERE [Extent1].[UnitPrice] < cast(10 as decimal(18))

 SELECT
    [Extent1].[CategoryID] AS [CategoryID],
    [Extent1].[CategoryName] AS [CategoryName],
    [Extent1].[Description] AS [Description],
    [Extent1].[Picture] AS [Picture]
    FROM [dbo].[Categories] AS [Extent1]
    WHERE [Extent1].[CategoryID] = @EntityKeyValue1

2つのSQLが発行されています。


書き換え後
 SELECT TOP (1)
    [Extent1].[ProductID] AS [ProductID],
    [Extent1].[ProductName] AS [ProductName],
    [Extent1].[SupplierID] AS [SupplierID],
    [Extent1].[CategoryID] AS [CategoryID],
    [Extent1].[QuantityPerUnit] AS [QuantityPerUnit],
    [Extent1].[UnitPrice] AS [UnitPrice],
    [Extent1].[UnitsInStock] AS [UnitsInStock],
    [Extent1].[UnitsOnOrder] AS [UnitsOnOrder],
    [Extent1].[ReorderLevel] AS [ReorderLevel],
    [Extent1].[Discontinued] AS [Discontinued],
    [Extent2].[CategoryID] AS [CategoryID1],
    [Extent2].[CategoryName] AS [CategoryName],
    [Extent2].[Description] AS [Description],
    [Extent2].[Picture] AS [Picture]
    FROM  [dbo].[Products] AS [Extent1]
    LEFT OUTER JOIN [dbo].[Categories] AS [Extent2] ON [Extent1].[CategoryID] =
    [Extent2].[CategoryID]
    WHERE [Extent1].[UnitPrice] < cast(10 as decimal(18))

書き換え後は、一つのSQL文の発行です。

Includeメソッドを使うもう一つのケースは、DbContextを破棄した後に、 関連テーブルのプロパティにアクセスしたい場合です。
例えば、以下のようなコードを書いたとします。

 static void Main(string[] args) {
      Database.SetInitializer<NorthwindContext>(null);
      var product = GetProduct();
      Console.WriteLine(product.Category.CategoryName);  // ★
      Console.ReadLine();
  }
  private static Product GetProduct() {
      using (var db = new NorthwindContext()) {
          db.SetLogging();
          var query = db.Products
                        .Where(p => p.UnitPrice < 10);
          return query.First();
      }
  }

このコードを実行すると、★の行で、'System.ObjectDisposedException' 例外が発生してしまいます。
これは、「すでに、DbContextが破棄されてるから、Categoryを取得する SQL文を発行できないよ」というエラー です。
こんな時に、Includeメソッド を使います。 GetProductメソッドを以下のように書き換えればOKです。

  private static Product GetProduct() {
      using (var db = new NorthwindContext()) {
          db.SetLogging();
          var query = db.Products
                        .Where(p => p.UnitPrice < 10)
                        .Include(nameof(Category));
          return query.First();
      }
  }


 

この記事へのトラックバックURL

http://trackback.blogsys.jp/livedoor/gushwell/52437370