NHibernate - обратные отношения
В этой главе мы рассмотрим еще одну особенность - обратные отношения. Это забавный вариант, который вы увидите в коллекции, которые обратно равны true, и он также сбивает с толку многих разработчиков. Итак, поговорим об этом варианте. Чтобы понять это, вам действительно нужно подумать о реляционной модели. Допустим, у вас есть двунаправленные ассоциации с использованием одного внешнего ключа.
С реляционной точки зрения у вас есть один внешний ключ, который представляет как клиента для заказа, так и заказы для клиента.
Из объектно-ориентированной модели у вас есть однонаправленные ассоциации с использованием этих ссылок.
Ничто не говорит о том, что две однонаправленные ассоциации представляют одну и ту же двунаправленную ассоциацию в базе данных.
Проблема в том, что у NHibernate недостаточно информации, чтобы знать, что customer.orders и order.customer представляют такое же отношение в базе данных.
Нам нужно предоставить inverse equals true в качестве подсказки, это потому, что однонаправленные ассоциации используют одни и те же данные.
Если мы попытаемся сохранить эти отношения, на которые есть 2 ссылки, NHibernate попытается обновить эту ссылку дважды.
Он фактически сделает дополнительный обход базы данных, а также будет иметь 2 обновления этого внешнего ключа.
Обратное равенство 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, который выделен. Вторая инструкция вставляется в таблицу заказов.
Вы заметите, что там установлен тот же идентификатор клиента 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.
Если мы посмотрим, как они вставляются, мы получим вставку в клиенте и вставку в заказы, но у нас нет этого дублирующего обновления внешнего ключа, потому что он обновляется при сохранении заказов.
Теперь вы должны заметить, что если у вас есть только однонаправленная ассоциация и это набор, который поддерживает эту связь, то, если вы установите обратное значение, равное истине, этот внешний ключ никогда не будет установлен, и эти элементы никогда не будут иметь свои внешние ключи, установленные в базе данных.
Если вы посмотрите на отношения «многие к одному» в Order.hbm.xml файл, и вы ищете обратный, у него фактически нет обратного атрибута.
Он всегда устанавливается из дочернего элемента, но если у вас есть коллекция «многие ко многим», вы можете установить ее с любой стороны.