В этой главе мы рассмотрим еще одну особенность — Обратные отношения. Это забавная опция, которую вы увидите в коллекции, которая обратно равна 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 и посмотрим, как оно его сохранило.
Вы заметите, что у нас есть 3 группы заявлений. Первый вставит клиента, а его идентификатором будет Guid, который выделен. Второе утверждение вставляется в таблицу заказов.
Вы заметите, что там установлен тот же Идентификатор клиента, так что установите этот внешний ключ. Последний оператор — это обновление, которое обновит внешний ключ с тем же идентификатором клиента еще раз.
Теперь проблема в том, что у клиента есть заказы, а у заказов есть клиент, и мы никоим образом не сказали 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 и ищите обратное, у него фактически нет обратного атрибута.
Он всегда устанавливается из дочернего элемента, но если у вас есть коллекция «многие ко многим», вы можете установить ее с любой стороны.