NHibernate - Inverse Beziehungen
In diesem Kapitel werden wir ein weiteres Merkmal behandeln, nämlich Inverse Beziehungen. Es ist eine amüsante Option, die Sie in einer Sammlung sehen werden, die umgekehrt gleich true ist, und sie verwirrt auch viele Entwickler. Sprechen wir also über diese Option. Um dies zu verstehen, muss man wirklich über das relationale Modell nachdenken. Angenommen, Sie haben bidirektionale Zuordnungen mit einem einzelnen Fremdschlüssel.
Aus relationaler Sicht haben Sie einen Fremdschlüssel, der sowohl Kundenbestellung als auch Kundenbestellung darstellt.
Aus dem OO-Modell haben Sie unidirektionale Assoziationen, die diese Referenzen verwenden.
Es gibt nichts, was besagt, dass zwei unidirektionale Assoziationen dieselbe bidirektionale Assoziation in der Datenbank darstellen.
Das Problem hierbei ist, dass NHibernate nicht über genügend Informationen verfügt, um dies zu wissen customer.orders und order.customer repräsentieren die gleiche Beziehung in der Datenbank.
Wir müssen liefern inverse equals true Dies ist ein Hinweis darauf, dass die unidirektionalen Zuordnungen dieselben Daten verwenden.
Wenn wir versuchen, diese Beziehungen mit zwei Verweisen zu speichern, versucht NHibernate, diese Referenz zweimal zu aktualisieren.
Es wird tatsächlich einen zusätzlichen Roundtrip zur Datenbank durchführen und es werden auch 2 Aktualisierungen dieses Fremdschlüssels vorgenommen.
Die Umkehrung gleich true teilt NHibernate mit, welche Seite der Beziehung ignoriert werden soll.
Wenn Sie es auf die Auflistungsseite anwenden, aktualisiert NHibernate den Fremdschlüssel immer von der anderen Seite, von der Seite des untergeordneten Objekts.
Dann haben wir nur ein Update für diesen Fremdschlüssel und wir haben keine zusätzlichen Updates für diese Daten.
Auf diese Weise können wir diese doppelten Aktualisierungen des Fremdschlüssels verhindern und Verstöße gegen Fremdschlüssel verhindern.
Werfen wir einen Blick auf die customer.cs Datei, in der Sie die sehen AddOrderMethode und die Idee hier ist, dass wir jetzt diesen Rückzeiger von der Bestellung zurück zum Kunden haben und er gesetzt werden muss. Wenn also eine Bestellung zu einem Kunden hinzugefügt wird, wird der Rückzeiger dieses Kunden gesetzt, andernfalls wäre er null. Daher benötigen wir dies, um diese im Objektdiagramm ordnungsgemäß miteinander zu verbinden.
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
}
}
Hier ist das Program.cs Dateiimplementierung.
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;
}
}
}
Es wird das in der Datenbank speichern und dann neu laden. Führen Sie nun Ihre Anwendung aus, öffnen Sie den NHibernate Profiler und sehen Sie, wie er tatsächlich gespeichert wurde.
Sie werden feststellen, dass wir 3 Gruppen von Aussagen haben. Der erste fügt den Kunden ein, und die ID dieses Kunden ist die Guid, die hervorgehoben ist. Die zweite Anweisung wird in die Auftragstabelle eingefügt.
Sie werden feststellen, dass dort dieselbe Kunden-ID-Richtlinie festgelegt ist. Stellen Sie daher den Fremdschlüssel ein. Die letzte Anweisung ist das Update, mit dem der Fremdschlüssel erneut auf dieselbe Kunden-ID aktualisiert wird.
Das Problem ist nun, dass der Kunde die Bestellungen hat und die Bestellungen den Kunden haben. Wir haben NHibernate auf keinen Fall gesagt, dass es sich tatsächlich um dieselbe Beziehung handelt. Die Art und Weise, wie wir dies tun, ist mit invers gleich wahr.
Also lass uns zu unserem gehen customer.hbm.xml Zuordnungsdatei und setzen Sie die Umkehrung auf true, wie im folgenden Code gezeigt.
<?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>
Beim Speichern der Bestellungen wird dieser Fremdschlüssel auf der Bestellseite festgelegt. Führen Sie nun diese Anwendung erneut aus und öffnen Sie den NHibernate-Profiler.
Wenn wir uns ansehen, wie diese eingefügt werden, erhalten wir die Einfügung im Kunden und die Einfügung in Bestellungen, aber wir haben diese doppelte Aktualisierung des Fremdschlüssels nicht, da sie aktualisiert wird, wenn die Bestellungen gespeichert werden.
Nun sollten Sie beachten, dass, wenn Sie nur eine unidirektionale Zuordnung haben und es die Menge ist, die diese Beziehung aufrechterhält, wenn Sie invers gleich true werden, dieser Fremdschlüssel niemals gesetzt wird und diese Elemente niemals ihre haben werden Fremdschlüssel in der Datenbank festgelegt.
Wenn Sie sich die Viele-zu-Eins-Beziehung in der Order.hbm.xml Datei und Sie suchen nach inversen, es hat eigentlich kein inverses Attribut.
Es wird immer aus dem untergeordneten Element festgelegt. Wenn Sie jedoch über eine Viele-zu-Viele-Sammlung verfügen, können Sie es von beiden Seiten festlegen.