NHibernate - odwrotne relacje
W tym rozdziale zajmiemy się inną funkcją, którą są relacje odwrotne. Jest to zabawna opcja, którą zobaczysz w kolekcji, która jest odwrotnie równa true, a także dezorientuje wielu programistów. Porozmawiajmy więc o tej opcji. Aby to zrozumieć, naprawdę musisz pomyśleć o modelu relacyjnym. Powiedzmy, że masz dwukierunkowe skojarzenia wykorzystujące pojedynczy klucz obcy.
Z relacyjnego punktu widzenia masz jeden klucz obcy, który reprezentuje zarówno klienta do zamówienia, jak i zamówienia do klienta.
Z modelu OO masz jednokierunkowe skojarzenia korzystające z tych odniesień.
Nic nie mówi, że dwie asocjacje jednokierunkowe reprezentują to samo powiązanie dwukierunkowe w bazie danych.
Problem polega na tym, że NHibernate nie ma wystarczających informacji, aby to wiedzieć customer.orders i order.customer reprezentują tę samą relację w bazie danych.
Musimy zapewnić inverse equals true jako wskazówka, dzieje się tak dlatego, że asocjacje jednokierunkowe używają tych samych danych.
Jeśli spróbujemy zapisać te relacje, które mają do nich 2 odniesienia, NHibernate spróbuje zaktualizować to odniesienie dwukrotnie.
W rzeczywistości wykona dodatkową podróż w obie strony do bazy danych, a także będzie mieć 2 aktualizacje tego klucza obcego.
Odwrotność równa się prawdzie mówi NHibernate, którą stronę związku należy zignorować.
Gdy zastosujesz go po stronie kolekcji, NHibernate zawsze zaktualizuje klucz obcy z drugiej strony, od strony obiektu podrzędnego.
Wtedy mamy tylko jedną aktualizację tego klucza obcego i nie mamy dodatkowych aktualizacji tych danych.
To pozwala nam zapobiegać takim zduplikowanym aktualizacjom klucza obcego, a także pomaga nam zapobiegać naruszeniom klucza obcego.
Rzućmy okiem na plik customer.cs plik, w którym zobaczysz plik AddOrdermetoda, a idea jest taka, że mamy teraz ten wskaźnik wsteczny od zamówienia do klienta i należy go ustawić. Więc kiedy zamówienie jest dodawane do klienta, jego tylny wskaźnik jest ustawiany, w przeciwnym razie byłby zerowy, więc potrzebujemy tego, aby utrzymać to poprawnie połączone w grafie obiektów.
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
}
}
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 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;
}
}
}
Ma zamiar zapisać to w bazie danych, a następnie załadować ponownie. Teraz uruchommy Twoją aplikację, otwórzmy NHibernate Profiler i zobaczmy, jak ją faktycznie zapisał.
Zauważysz, że mamy 3 grupy zdań. Pierwsza wstawia klienta, a identyfikator klienta to Guid, który jest podświetlony. Drugi wyciąg jest wstawiany do tabeli zamówień.
Zauważysz, że jest tam ustawiony ten sam identyfikator klienta, więc miej ustawiony klucz obcy. Ostatnią instrukcją jest aktualizacja, która ponownie zaktualizuje klucz obcy do tego samego identyfikatora klienta.
Problem polega na tym, że klient ma zamówienia, a zamówienia klient, nie ma mowy, żebyśmy nie powiedzieli NHibernate, że to w rzeczywistości ta sama relacja. Sposób, w jaki to robimy, jest taki, że odwrotność równa się prawda.
Więc przejdźmy do naszego customer.hbm.xml mapping file i ustaw wartość odwrotną równą true, jak pokazano w poniższym kodzie.
<?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>
Zapisując zamówienia, ustawi ten klucz obcy od strony zamówienia. Teraz uruchommy ponownie tę aplikację i otwórzmy profiler NHibernate.
Jeśli spojrzymy na to, jak są one wstawiane, otrzymujemy wkładkę u klienta i wkładkę do zamówień, ale nie mamy tej zduplikowanej aktualizacji klucza obcego, ponieważ jest on aktualizowany podczas zapisywania zamówień.
Teraz powinieneś zauważyć, że jeśli masz tylko jednokierunkowe skojarzenie i to zestaw utrzymuje tę zależność, to jeśli odwrócisz odwrotność równa się prawdzie, ten klucz obcy nigdy nie zostanie ustawiony, a te elementy nigdy nie będą miały swojego klucze obce ustawione w bazie danych.
Jeśli spojrzysz na relację wiele do jednego w Order.hbm.xml file i szukasz inverse, w rzeczywistości nie ma on atrybutu inverse.
Zawsze jest ustawiany na podstawie elementu podrzędnego, ale jeśli masz kolekcję wiele do wielu, możesz ustawić ją z dowolnej strony.