NHibernate - Relazioni inverse
In questo capitolo, tratteremo un'altra caratteristica che è Relazioni inverse. È un'opzione divertente che vedrai nella raccolta che è inversamente uguale a true e confonde anche molti sviluppatori. Quindi parliamo di questa opzione. Per capirlo bisogna proprio pensare al modello relazionale. Supponiamo che tu abbia associazioni bidirezionali che utilizzano una singola chiave esterna.
Da un punto di vista relazionale, hai una chiave esterna e rappresenta sia il cliente all'ordine che gli ordini al cliente.
Dal modello OO, hai associazioni unidirezionali che utilizzano questi riferimenti.
Non c'è nulla che dica che due associazioni unidirezionali rappresentino la stessa associazione bidirezionale nel database.
Il problema qui è che NHibernate non ha abbastanza informazioni per saperlo customer.orders e order.customer rappresentano la stessa relazione nel database.
Dobbiamo provvedere inverse equals true come suggerimento, è perché le associazioni unidirezionali utilizzano gli stessi dati.
Se proviamo a salvare queste relazioni che hanno 2 riferimenti ad esse, NHibernate proverà ad aggiornare quel riferimento due volte.
Effettuerà effettivamente un ulteriore viaggio di andata e ritorno al database e avrà anche 2 aggiornamenti a quella chiave esterna.
L'inverso uguale a vero indica a NHibernate quale lato della relazione ignorare.
Quando lo applichi al lato della raccolta e NHibernate aggiornerà sempre la chiave esterna dall'altro lato, dal lato dell'oggetto figlio.
Quindi abbiamo solo un aggiornamento per quella chiave esterna e non abbiamo aggiornamenti aggiuntivi per quei dati.
Questo ci consente di impedire questi aggiornamenti duplicati alla chiave esterna e ci aiuta anche a prevenire violazioni della chiave esterna.
Diamo un'occhiata al file customer.cs file in cui vedrai il file AddOrdere l'idea qui è che ora abbiamo questo puntatore indietro dall'ordine al cliente e deve essere impostato. Quindi, quando un ordine viene aggiunto a un cliente, viene impostato il puntatore a ritroso di quel cliente, altrimenti sarebbe nullo, quindi abbiamo bisogno di questo per mantenerlo collegato correttamente nell'oggetto grafico.
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
}
}
Ecco il file Program.cs implementazione del file.
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;
}
}
}
Lo salverà nel database e poi lo ricaricherà. Ora eseguiamo la tua applicazione e apriamo NHibernate Profiler e vediamo come l'ha effettivamente salvata.
Noterai che abbiamo 3 gruppi di dichiarazioni. Il primo inserirà il cliente e l'ID di quel cliente è il Guid, che viene evidenziato. La seconda istruzione viene inserita nella tabella degli ordini.
Noterai che lo stesso ID cliente Guid è impostato lì, quindi imposta quella chiave esterna. L'ultima istruzione è l'aggiornamento, che aggiornerà nuovamente la chiave esterna con lo stesso ID cliente.
Ora il problema è che il cliente ha gli ordini e gli ordini hanno il cliente, non è possibile che non abbiamo detto a NHibernate che in realtà è la stessa relazione. Il modo in cui lo facciamo è con inverso uguale a vero.
Quindi andiamo al nostro customer.hbm.xml mapping del file e impostare l'inverso uguale a true come mostrato nel codice seguente.
<?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>
Durante il salvataggio degli ordini, imposterà quella chiave esterna dal lato dell'ordine. Ora eseguiamo di nuovo questa applicazione e apriamo il profiler NHibernate.
Se guardiamo come vengono inseriti, otteniamo l'inserimento nel cliente e l'inserimento negli ordini, ma non abbiamo quell'aggiornamento duplicato della chiave esterna perché viene aggiornato quando gli ordini vengono salvati.
Ora, dovresti notare che se hai solo un'associazione unidirezionale ed è l'insieme che mantiene questa relazione, allora se giri inverso uguale a vero, quella chiave esterna non sarà mai impostata e quegli elementi non avranno mai il loro chiavi esterne impostate nel database.
Se guardi alla relazione molti-a-uno in Order.hbm.xml file e cerchi inverso, in realtà non ha un attributo inverso.
Viene sempre impostato dall'elemento figlio, ma se si dispone di una raccolta molti a molti, è possibile impostarla da entrambi i lati.