NHibernate-遅延読み込み

この章では、遅延読み込み機能について説明します。これはデフォルトではまったく異なる概念であり、NHibernateには遅延読み込みがありません。たとえば、顧客を読み込む場合、すべての注文が読み込まれるわけではありません。

  • 注文コレクションはオンデマンドで読み込まれます。

  • 多対1であろうとコレクションであろうと、関連付けはデフォルトで遅延読み込みされるため、 Open ISession

  • セッションを閉じた場合、またはトランザクションをコミットした場合、それらの追加オブジェクトをプルできない遅延読み込み例外が発生する可能性があります。

  • 遅延読み込みと実際に必要なデータ量に注意する必要があります。

  • アソシエーション全体の遅延読み込みをオフにするか、遅延をfalseに設定するか、フェッチ戦略を指定することもできます。

これが Program.cs ファイルの実装。

using System; 
using System.Data; 
using System.Linq; 
using System.Reflection; 

using HibernatingRhinos.Profiler.Appender.NHibernate; 
using NHibernate.Cfg; 
using NHibernate.Dialect; 
using NHibernate.Driver; 
using NHibernate.Linq;

namespace NHibernateDemo { 

   internal class Program { 
	
      private static void Main() { 
		
         var cfg = ConfigureNHibernate(); 
         var sessionFactory = cfg.BuildSessionFactory();
         
         Guid id; 
         using(var session = sessionFactory.OpenSession()) 
			
         using(var tx = session.BeginTransaction()) {
            var newCustomer = CreateCustomer(); 
            Console.WriteLine("New Customer:"); 
            Console.WriteLine(newCustomer); 
            session.Save(newCustomer); 
            id = newCustomer.Id; 
            tx.Commit(); 
         }
         
         using(var session = sessionFactory.OpenSession()) 
			
         using(var tx = session.BeginTransaction()) { 
            var reloaded = session.Load<Customer>(id); 
            Console.WriteLine("Reloaded:"); 
            Console.WriteLine(reloaded); 
            Console.WriteLine("The orders were ordered by: "); 
            
            foreach (var order in reloaded.Orders) { 
               Console.WriteLine(order.Customer); 
            } 
				
            tx.Commit(); 
         }
			
         Console.WriteLine("Press <ENTER> to exit..."); 
         Console.ReadLine(); 
      }
		
      private static Customer CreateCustomer() { 
         
         var customer = new Customer { 
            FirstName = "John", 
            LastName = "Doe", 
            Points =100, 
            HasGoldStatus = true, 
            MemberSince = new DateTime(2012, 1, 1),
            CreditRating = CustomerCreditRating.Good,
            AverageRating = 42.42424242, 
            Address = CreateLocation() 
         }; 
			
         var order1 = new Order { Ordered = DateTime.Now }; 
         customer.AddOrder(order1); 
         
         var order2 = new Order { 
            Ordered = DateTime.Now.AddDays(-1), 
            Shipped = DateTime.Now, 
            ShipTo = CreateLocation() 
         }; 
			
         customer.AddOrder(order2); return customer; 
      }
		
      private static Location CreateLocation() { 
         return new Location { 
            Street = "123 Somewhere Avenue", 
            City = "Nowhere", 
            Province = "Alberta", 
            Country = "Canada" 
         }; 
      }
		
      private static Configuration ConfigureNHibernate() { 
		
         NHibernateProfiler.Initialize(); 
         var cfg = new Configuration(); 
         
         cfg.DataBaseIntegration(x => { 
            x.ConnectionStringName = "default"; 
            x.Driver<SqlClientDriver>(); 
            x.Dialect<MsSql2008Dialect<(); 
            x.IsolationLevel = IsolationLevel.RepeatableRead; 
            x.Timeout = 10;
            x.BatchSize = 10;
         }); 
         
         cfg.SessionFactory().GenerateStatistics();
         cfg.AddAssembly(Assembly.GetExecutingAssembly()); 
         return cfg; 
      } 
   } 
}

これを理解するために、アプリケーションを実行してNHibernateプロファイラーを見てみましょう。

ご覧のとおり、特定の顧客IDが指定されたSelect From Customerがあり、実際にその顧客のコレクションにアクセスすると、別のSelect FromOrdersテーブルもあります。

したがって、データベースへの往復は2回あります。さて、時々、これを最適化したいでしょう。これを行うには、に行きましょうcustomer.hbm.xml ファイルを作成し、フェッチ戦略を追加して、結合フェッチを実行するように依頼します。

<?xml version = "1.0" encoding = "utf-8" ?> 
<hibernate-mapping xmlns = "urn:nhibernate-mapping-2.2" assembly = "NHibernateDemo"
   namespace = "NHibernateDemo"> 
   
   <class name = "Customer"> 
	
      <id name = "Id"> 
         <generator class = "guid.comb"/> 
      </id> 
   
      <property name = "FirstName"/> 
      <property name = "LastName"/> 
      <property name = "AverageRating"/> 
      <property name = "Points"/> 
      <property name = "HasGoldStatus"/> 
      <property name = "MemberSince" type = "UtcDateTime"/> 
      <property name = "CreditRating" type = "CustomerCreditRatingType"/>
      
      <component name = "Address"> 
         <property name = "Street"/> 
         <property name = "City"/> 
         <property name = "Province"/> 
         <property name = "Country"/> 
      </component>
      
      <set name = "Orders" table = "`Order`" cascade = "all-delete-orphan" 
         fetch = "join"> 
         <key column = "CustomerId"/> 
         <one-to-many class = "Order"/> 
      </set> 
   
   </class> 
</hibernate-mapping>

アプリケーションのコードを変更していないことがわかるように、フェッチ戦略を追加しました。 customer.hbm.xml。このアプリケーションをもう一度実行してみましょう。それでもまったく同じように動作します。NHibernateプロファイラーを見てみましょう。

  • 以前は、プログラムはデータベースに2回往復していましたが、現在は1回しかありません。これは、ここで左外部結合を実行しているためです。

  • 顧客IDに基づいて、顧客テーブルと注文テーブルの間で左外部結合を実行していることがわかります。したがって、すべての情報を一度にロードできます。

  • データベースへの往復を1回保存しました。

  • 欠点は、顧客情報が両方の行に複製されることです。これが、SQLの左外部結合が機能する方法です。

  • したがって、フェッチ戦略を使用すると、もう少しデータをプルバックし、ラウンドトリップを節約できます。

これはクエリレベルでも実行できるので、 Program.cs ファイルを作成し、より単純なリロードされた例を見てください。

using(var session = sessionFactory.OpenSession()) 

using(var tx = session.BeginTransaction()) { 
   //var query = from customer in session.Query<Customer>() 
   // select customer; 
   //var reloaded = query.Fetch(x => x.Orders).ToList();
	
   var reloaded = session.Load<Customer>(id); 
   Console.WriteLine("Reloaded:"); 
   Console.WriteLine(reloaded); 
   Console.WriteLine("The orders were ordered by: "); 
   
   foreach (var order in reloaded.Orders) { 
      Console.WriteLine(order.Customer); 
   } 
	
   tx.Commit(); 
}

ここでは、お客様によるロードを行っています。それをクエリに変更してみましょう。次のコードに示すように、リンククエリを使用します。

using(var session = sessionFactory.OpenSession()) 

using(var tx = session.BeginTransaction()) {
   var query = from customer in session.Query<Customer>() 
   where customer.Id == id select customer; 
   var reloaded = query.Fetch(x => x.Orders).ToList().First();
	
   Console.WriteLine("Reloaded:"); 
   Console.WriteLine(reloaded); 
	
   tx.Commit();
}

からフェッチ戦略も削除しましょう customer.hbm.xml ファイル。

<?xml version = "1.0" encoding = "utf-8" ?> 
<hibernate-mapping xmlns = "urn:nhibernate-mapping-2.2" assembly = "NHibernateDemo"
   namespace = "NHibernateDemo"> 

   <class name = "Customer"> 
	
      <id name = "Id"> 
         <generator class = "guid.comb"/> 
      </id> 
   
      <property name = "FirstName"/> 
      <property name = "LastName"/>
      <property name = "AverageRating"/> 
      <property name = "Points"/> 
      <property name = "HasGoldStatus"/> 
      <property name = "MemberSince" type = "UtcDateTime"/> 
      <property name = "CreditRating" type = "CustomerCreditRatingType"/>
   
      <component name = "Address"> 
         <property name = "Street"/> 
         <property name = "City"/> 
         <property name = "Province"/> 
         <property name = "Country"/> 
      </component>
   
      <set name = "Orders" table = "`Order`" cascade = "all-delete-orphan"> 
         <key column = "CustomerId"/> 
         <one-to-many class = "Order"/> 
      </set> 
   
   </class> 
</hibernate-mapping>

このアプリケーションをもう一度実行すると、次の出力が表示されます。

New Customer:
John Doe (00000000-0000-0000-0000-000000000000)
   Points: 100
   HasGoldStatus: True
   MemberSince: 1/1/2012 12:00:00 AM (Unspecified)
   CreditRating: Good
   AverageRating: 42.42424242

   Orders:
      Order Id: 00000000-0000-0000-0000-000000000000
      Order Id: 00000000-0000-0000-0000-000000000000

Reloaded:
John Doe (6ebacd17-f9ba-4ad8-9817-a5bb01112a5a)
   Points: 100
   HasGoldStatus: True
   MemberSince: 1/1/2012 12:00:00 AM (Utc)
   CreditRating: Good
   AverageRating: 42.4242

   Orders:
      Order Id: 16a6596b-d56e-41c7-9681-a5bb01112a60
      Order Id: d41d615b-0f21-4032-81db-a5bb01112a61
		
Press <ENTER> to exit...

NHibernateプロファイラーを見てみましょう。この熱心な結合フェッチが再び発生していることがわかりますが、今回はクエリに基づいています。