Nhibernate-lazy-loading

提供:Dev Guides
移動先:案内検索

NHibernate-遅延読み込み

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

  • 注文コレクションはオンデマンドでロードされます。
  • 多対一であってもコレクションであっても、デフォルトで関連付けが遅延ロードされる場合は、 Open ISession が必要です。
  • セッションを閉じた場合、またはトランザクションをコミットした場合、これらの追加オブジェクトをプルできない遅延読み込み例外を取得できます。
  • 遅延読み込みと実際に必要なデータ量に注意する必要があります。
  • アソシエーション全体の遅延読み込みをオフにするか、lazy equals 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 Profilerを見てみましょう。

顧客コレクション

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

したがって、データベースへの往復は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 Profilerを見てみましょう。

顧客プロファイル

  • 以前は、プログラムはデータベースへの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 Profilerを見てみましょう。この熱心な結合フェッチが再び発生していることがわかりますが、今回はクエリに基づいています。

Join Fetch