Учебники

NHibernate — обратные отношения

В этой главе мы рассмотрим еще одну особенность — Обратные отношения. Это забавная опция, которую вы увидите в коллекции, которая обратно равна true, и это также сбивает с толку многих разработчиков. Итак, давайте поговорим об этом варианте. Чтобы понять это, вам действительно нужно подумать о реляционной модели. Допустим, у вас есть двунаправленные ассоциации, использующие один внешний ключ.

  • С точки зрения отношений у вас есть один внешний ключ, и он представляет собой как заказчика, так и заказы заказчику.

  • От модели ОО, у вас есть однонаправленные ассоциации, использующие эти ссылки.

  • Ничто не говорит о том, что две однонаправленные ассоциации представляют одну и ту же двунаправленную ассоциацию в базе данных.

  • Проблема в том, что NHibernate не имеет достаточно информации, чтобы знать, что customer.orders и order.customer представляют одинаковые отношения в базе данных.

  • Нам нужно предоставить нам обратное значение true в качестве подсказки, поскольку однонаправленные ассоциации используют одни и те же данные.

  • Если мы попытаемся сохранить эти отношения, которые имеют 2 ссылки на них, NHibernate попытается обновить эту ссылку дважды.

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

  • Обратное значение true указывает NHibernate, какую сторону отношений игнорировать.

  • Когда вы применяете его на стороне коллекции, NHibernate всегда обновляет внешний ключ с другой стороны, со стороны дочернего объекта.

  • Тогда у нас есть только одно обновление для этого внешнего ключа, и у нас нет дополнительных обновлений для этих данных.

  • Это позволяет нам предотвращать дублирование обновлений внешнего ключа, а также предотвращает нарушения внешнего ключа.

С точки зрения отношений у вас есть один внешний ключ, и он представляет собой как заказчика, так и заказы заказчику.

От модели ОО, у вас есть однонаправленные ассоциации, использующие эти ссылки.

Ничто не говорит о том, что две однонаправленные ассоциации представляют одну и ту же двунаправленную ассоциацию в базе данных.

Проблема в том, что NHibernate не имеет достаточно информации, чтобы знать, что customer.orders и order.customer представляют одинаковые отношения в базе данных.

Нам нужно предоставить нам обратное значение true в качестве подсказки, поскольку однонаправленные ассоциации используют одни и те же данные.

Если мы попытаемся сохранить эти отношения, которые имеют 2 ссылки на них, NHibernate попытается обновить эту ссылку дважды.

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

Обратное значение true указывает NHibernate, какую сторону отношений игнорировать.

Когда вы применяете его на стороне коллекции, NHibernate всегда обновляет внешний ключ с другой стороны, со стороны дочернего объекта.

Тогда у нас есть только одно обновление для этого внешнего ключа, и у нас нет дополнительных обновлений для этих данных.

Это позволяет нам предотвращать дублирование обновлений внешнего ключа, а также предотвращает нарушения внешнего ключа.

Давайте посмотрим на файл customer.cs, в котором вы увидите метод AddOrder, и идея заключается в том, что теперь у нас есть этот обратный указатель от заказа к клиенту, и его нужно установить. Поэтому, когда заказ добавляется к клиенту, указатель возврата этого клиента устанавливается, в противном случае он будет нулевым, поэтому нам нужно это, чтобы правильно связать это вместе в графе объектов.

using System; 
using System.Text; 
using Iesi.Collections.Generic;

namespace NHibernateDemo {
 
   public class Customer { 
      
      public Customer() {
         MemberSince = DateTime.UtcNow; Orders = new HashedSet<Order>();
      } 
      
      public virtual Guid Id { get; set; } 
      public virtual string FirstName { get; set; } 
      public virtual string LastName { get; set; } 
      public virtual double AverageRating { get; set; } 
      public virtual int Points { get; set; } 
      public virtual bool HasGoldStatus { get; set; } 
		
      public virtual DateTime MemberSince { get; set; } 
      public virtual CustomerCreditRating CreditRating { get; set; } 
      public virtual Location Address { get; set; }
      public virtual ISet<Order> Orders { get; set; }
      public virtual void AddOrder(Order order) { Orders.Add(order); order.Customer = this; }
      
      public override string ToString() { 
         var result = new StringBuilder(); 
			
         result.AppendFormat("{1} {2} ({0})\r\n\tPoints: {3}\r\n\tHasGoldStatus:
            {4}\r\n\tMemberSince: {5} ({7})\r\n\tCreditRating: {6}\r\n\tAverageRating:
            {8}\r\n", Id, FirstName, LastName, Points, HasGoldStatus, MemberSince,
            CreditRating, MemberSince.Kind, AverageRating);
         result.AppendLine("\tOrders:"); 
         
         foreach(var order in Orders) { 
            result.AppendLine("\t\t" + order); 
         } 
			
         return result.ToString(); 
      } 
   }
   
   public class Location { 
      public virtual string Street { get; set; } 
      public virtual string City { get; set; } 
      public virtual string Province { get; set; } 
      public virtual string Country { get; set; }
   } 
   
   public enum CustomerCreditRating { 
      Excellent, 
      VeryVeryGood, 
      VeryGood, 
      Good, 
      Neutral, 
      Poor, 
      Terrible 
   } 
}

Вот реализация файла 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 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(); 
         }
			
         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 и посмотрим, как оно его сохранило.

Обратный профилировщик NHibernate

Вы заметите, что у нас есть 3 группы заявлений. Первый вставит клиента, а его идентификатором будет Guid, который выделен. Второе утверждение вставляется в таблицу заказов.

Идентификатор клиента

Вы заметите, что там установлен тот же Идентификатор клиента, так что установите этот внешний ключ. Последний оператор — это обновление, которое обновит внешний ключ с тем же идентификатором клиента еще раз.

Клиент Hbm

Теперь проблема в том, что у клиента есть заказы, а у заказов есть клиент, и мы никоим образом не сказали NHibernate, что это на самом деле те же отношения. То, как мы это делаем, с обратным равен true.

Итак, давайте перейдем к нашему файлу сопоставления customer.hbm.xml и установим обратное значение равным true, как показано в следующем коде.

<?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" 
         inverse = "true"> 
         <key column = "CustomerId"/> 
         <one-to-many class = "Order"/> 
      </set> 
   
   </class> 
</hibernate-mapping>

При сохранении заказов он устанавливает этот внешний ключ со стороны заказа. Теперь давайте снова запустим это приложение и откроем профилировщик NHibernate.

Иностранный ключ

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

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

Если вы посмотрите на отношение «многие к одному» в файле Order.hbm.xml и ищите обратное, у него фактически нет обратного атрибута.

Он всегда устанавливается из дочернего элемента, но если у вас есть коллекция «многие ко многим», вы можете установить ее с любой стороны.