NHibernate - Ленивая загрузка

В этой главе мы рассмотрим функцию отложенной загрузки. По умолчанию это совершенно другая концепция, и в NHibernate нет отложенной загрузки, например, если вы загружаете клиента, он не загружает все заказы.

  • Коллекция заказов будет загружена по запросу.

  • Любая ассоциация, будь то многие-к-одному или коллекция, загружается лениво по умолчанию, для нее требуется Open ISession.

  • Если вы закрыли сеанс или зафиксировали транзакцию, вы можете получить исключение ленивой загрузки, которое не может извлечь эти дополнительные объекты.

  • Вы должны быть осторожны с отложенной загрузкой и тем, сколько данных вам действительно нужно.

  • Вы можете отключить ленивую загрузку для всей ассоциации или поставить lazy равным 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.

Как вы можете видеть, у нас есть 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.

  • Раньше у программы было два обращения к базе данных, теперь у нее только один, и это потому, что здесь выполняется левое внешнее соединение.

  • Мы видим, что он выполняет левое внешнее соединение между таблицей клиентов и таблицей заказов на основе идентификатора клиента, и, следовательно, он может загрузить всю эту информацию одновременно.

  • Мы сохранили 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, вы увидите, что у нас снова происходит активная загрузка соединения, но на этот раз она основана на запросе.