В этой главе мы рассмотрим функцию отложенной загрузки. По умолчанию это совершенно другая концепция, и NHibernate не требует отложенной загрузки, например, если вы загружаете клиента, он не собирается загружать все заказы.
-
Коллекция заказов будет загружена по запросу.
-
Любая ассоциация, будь то много-к-одному или коллекция, загружается по умолчанию лениво, требует Open ISession .
-
Если вы закрыли сеанс или зафиксировали транзакцию, вы можете получить исключение для отложенной загрузки, которое не сможет добавить эти дополнительные объекты.
-
Вы должны быть осторожны с отложенной загрузкой и объемом данных, которые вам действительно нужны.
-
Вы можете отключить ленивую загрузку для всей ассоциации, или вы можете поставить ленивый равным false, или вы также можете указать стратегию выборки.
Коллекция заказов будет загружена по запросу.
Любая ассоциация, будь то много-к-одному или коллекция, загружается по умолчанию лениво, требует 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 Profiler.
Как вы можете видеть, у нас есть «Выбор из клиента» с заданным идентификатором клиента, а затем у нас есть еще одна таблица «Выбор из заказов», когда она фактически обращается к коллекции этого клиента.
Итак, у нас есть 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.
-
Таким образом, с помощью стратегии извлечения мы собираем немного больше данных и сохраняем туда-обратно.
Раньше у программы было два обхода базы данных, теперь у нее есть только одна, и это потому, что здесь выполняется левое внешнее соединение.
Мы видим, что он выполняет левое внешнее соединение между таблицей клиентов и таблицей заказов на основе идентификатора клиента и, следовательно, может загружать всю эту информацию одновременно.
Мы сохранили 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, вы можете видеть, что у нас снова происходит эта нетерпеливая выборка соединения, но на этот раз она основана на запросе.