2015年10月08日

EntityFramework(8):遅延読み込み

  
以下のコードを見てください。

  private static void ProcessCustomer(Customer customer) {
      Console.WriteLine(customer.CompanyName);
  }
public static void Sample() { using (var db = new NorthwindContext()) { db.Database.Log = sql => { Console.Write(sql); }; var query = from ord in db.Orders where ord.ShipVia == 3 select ord; foreach (Order order in query) { if (order.ShipCountry == "Brazil") { Console.WriteLine("//////////////////"); ProcessCustomer(order.Customer); } } } }

今回は、処理のSQLの発行タイミングを見るために、ログをコンソールに出力しています。

foreachの中で、orderオブジェクトのCustomerプロパティを参照しています。 このOrderクラスのCustomerプロパティの値はどこで設定されるのでしょうか?

query変数に設定されたクエリが実行されると、要求したオブジェクトだけが実際 に取得されます。関連オブジェクトが自動で同時に取得されることはありません。 つまり、if 文が実行されたときには、まだ、Customerプロパティの値は不定です。
ProcessCustomerメソッドを呼び出したとき(つまり、関連オブジェクトである Customerプロパティにアクセスしようとしたとき)に、初めてCustomerのデータ が取得されます。 これを遅延読み込みと言います。

※ 遅延実行と遅延読み込みは別の概念です。

さらに素晴らしいのは、同一の Customer については、最初の一回のみ SQL が発行されてるという点です。
一度該当する Order の Customer プロパティに値が設定されれば、 2度目のアクセスでは、キャッシュされた値が利用されます。
上のコードを実行してみてもらえればわかりますが、 同一顧客の読み込み(SQL文の発行)は最初の一回だけであることが確認できます。



では、以下のコードはどうでしょうか。

 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);
 }

このコードでは、First()で即時実行されますが、 WriteLine()実行まで、該当するCategoryテーブルが読み込まれることはありません。
実際に必要になったときに、必要になった分だけを取得するということを Entity Framework がやってくれます。

Logファイルを確認すると、以下の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]
    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

でも、場合によっては、複数のSQL文が発行されることで、 パフォーマンスに影響を与える場合も有り得ます。
そんなときの対策も用意されています。それは次回に。



おまけ - 遅延読み込みをオフにする

 db.Configuration.LazyLoadingEnabled = false;

とすると、遅延読み込みをオフにできます。 この場合、関連エンティティを参照すると、System.NullReferenceException が発生します。

 


 

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

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