NHibernate - Relations inverses
Dans ce chapitre, nous couvrirons une autre fonctionnalité qui est les relations inverses. C'est une option amusante que vous verrez sur une collection qui est inversement égale à true et qui déroute également beaucoup de développeurs. Parlons donc de cette option. Pour comprendre cela, il faut vraiment penser au modèle relationnel. Supposons que vous ayez des associations bidirectionnelles utilisant une seule clé étrangère.
D'un point de vue relationnel, vous disposez d'une clé étrangère, qui représente à la fois le client à commander et les commandes au client.
À partir du modèle OO, vous avez des associations unidirectionnelles utilisant ces références.
Rien n'indique que deux associations unidirectionnelles représentent la même association bidirectionnelle dans la base de données.
Le problème ici est que NHibernate n'a pas assez d'informations pour savoir que customer.orders et order.customer représentent la même relation dans la base de données.
Nous devons fournir inverse equals true à titre indicatif, c'est parce que les associations unidirectionnelles utilisent les mêmes données.
Si nous essayons de sauvegarder ces relations qui ont 2 références, NHibernate essaiera de mettre à jour cette référence deux fois.
Il effectuera en fait un aller-retour supplémentaire vers la base de données, et il aura également 2 mises à jour de cette clé étrangère.
L'inverse est égal à vrai indique à NHibernate quel côté de la relation ignorer.
Lorsque vous l'appliquez du côté collection et NHibernate mettra toujours à jour la clé étrangère de l'autre côté, du côté objet enfant.
Ensuite, nous n'avons qu'une seule mise à jour de cette clé étrangère et nous n'avons pas de mises à jour supplémentaires pour ces données.
Cela nous permet d'éviter ces mises à jour en double de la clé étrangère et cela nous aide également à empêcher les violations de clé étrangère.
Jetons un coup d'œil à la customer.cs fichier dans lequel vous verrez le AddOrderet l'idée ici est que nous avons maintenant ce pointeur de retour de la commande au client et qu'il doit être défini. Ainsi, lorsqu'une commande est ajoutée à un client, le pointeur arrière de ce client est défini, sinon il serait nul, nous en avons donc besoin pour que cela reste correctement connecté dans le graphique d'objets.
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
}
}
Voici la Program.cs implémentation de fichier.
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;
}
}
}
Il va l'enregistrer dans la base de données, puis la recharger. Maintenant, exécutons votre application et ouvrons le NHibernate Profiler et voyons comment il l'a réellement enregistré.
Vous remarquerez que nous avons 3 groupes de déclarations. Le premier insérera le client, et l'ID de ce client est le Guid, qui est mis en surbrillance. La deuxième instruction est insérée dans la table des commandes.
Vous remarquerez que le même Guid ID client est défini là-dedans, alors ayez cette clé étrangère définie. La dernière instruction est la mise à jour, qui mettra à nouveau à jour la clé étrangère avec le même identifiant client.
Maintenant, le problème est que le client a les commandes, et les commandes ont le client, il n'y a aucun moyen que nous n'ayons pas dit à NHibernate que c'est en fait la même relation. La façon dont nous faisons cela est avec inverse égal à vrai.
Alors allons à notre customer.hbm.xml mappage et définissez l'inverse sur true comme indiqué dans le code suivant.
<?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>
Lors de l'enregistrement des commandes, il définira cette clé étrangère du côté de la commande. Maintenant, exécutons à nouveau cette application et ouvrons le profileur NHibernate.
Si nous regardons comment ceux-ci sont insérés, nous obtenons l'insertion dans le client et l'insertion dans les commandes, mais nous n'avons pas cette mise à jour en double de la clé étrangère car elle est mise à jour lorsque les commandes sont enregistrées.
Maintenant, vous devez noter que si vous n'avez qu'une association unidirectionnelle et que c'est l'ensemble qui maintient cette relation, alors si vous activez l'inverse est égal à vrai, cette clé étrangère ne sera jamais définie et ces éléments n'auront jamais leur clés étrangères définies dans la base de données.
Si vous regardez la relation plusieurs à un dans le Order.hbm.xml fichier et que vous recherchez l'inverse, il n'a en fait pas d'attribut inverse.
Il est toujours défini à partir de l'élément enfant, mais si vous avez une collection plusieurs-à-plusieurs, vous pouvez la définir de chaque côté.