NHibernate - Leniwe ładowanie

W tym rozdziale zajmiemy się funkcją leniwego ładowania. Domyślnie jest to zupełnie inna koncepcja, a NHibernate nie ma leniwego ładowania, na przykład jeśli ładujesz klienta, nie załaduje wszystkich zamówień.

  • Kolekcja zamówień zostanie załadowana na żądanie.

  • Każde skojarzenie, niezależnie od tego, czy jest to wiele do jednego, czy kolekcja, jest domyślnie ładowane z opóźnieniem, wymaga rozszerzenia Open ISession.

  • Jeśli zamknąłeś sesję lub zatwierdziłeś transakcję, możesz uzyskać wyjątek z opóźnionym ładowaniem, który nie może pobrać tych dodatkowych obiektów.

  • Musisz uważać na leniwe ładowanie i ile danych faktycznie potrzebujesz.

  • Możesz wyłączyć leniwe ładowanie dla całego skojarzenia lub ustawić leniwe równa się fałsz lub możesz również określić strategię pobierania.

Tutaj jest Program.cs implementacja plików.

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

Aby to zrozumieć, uruchommy aplikację i przyjrzyjmy się NHibernate Profiler.

Jak widać, mamy opcję Select From Customer z określonym identyfikatorem klienta, a także inną tabelę Select From Orders, która faktycznie uzyskuje dostęp do kolekcji tego klienta.

Mamy więc 2 wycieczki do bazy danych. Czasami chcielibyśmy to zoptymalizować. Aby to zrobić, przejdźmy docustomer.hbm.xml file i dodaj strategię pobierania i poproś o wykonanie operacji pobierania z dołączeniem.

<?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>

Jak widać, nie zmieniliśmy żadnego kodu w naszej aplikacji, właśnie dodaliśmy strategię pobierania w customer.hbm.xml. Uruchommy ponownie tę aplikację, nadal zachowuje się dokładnie tak samo. Spójrzmy na NHibernate Profiler.

  • Wcześniej program miał dwie rundy do bazy danych, teraz ma tylko jedną, a to dlatego, że wykonuje tutaj lewe sprzężenie zewnętrzne.

  • Widzimy, że wykonuje lewe zewnętrzne połączenie między tabelą klientów a tabelą zamówień na podstawie identyfikatora klienta, a zatem jest w stanie załadować wszystkie te informacje naraz.

  • Zapisaliśmy 1 podróż w obie strony do bazy danych.

  • Wadą jest to, że informacje o kliencie zostaną zduplikowane w obu wierszach i tak działa lewe sprzężenie zewnętrzne SQL.

  • Tak więc dzięki strategii pobierania pobieramy nieco więcej danych i oszczędzamy podróż w obie strony.

Możesz to również zrobić na poziomie zapytania, więc przejdźmy do Program.cs plik i spójrz na prostszy, ponownie załadowany przykład.

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

Tutaj robimy ładunek przez klienta. Teraz zmieńmy to na zapytanie i użyjemy zapytania łączącego, jak pokazano w poniższym kodzie.

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

Usuńmy również strategię pobierania z customer.hbm.xml plik.

<?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>

Uruchommy ponownie tę aplikację, a zobaczysz następujące dane wyjściowe.

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...

Teraz spójrzmy na NHibernate Profiler, widzisz, że mamy to niecierpliwe pobieranie dołączeń, które ma miejsce ponownie, ale tym razem jest oparte na zapytaniu.