NHibernate - Relações Inversas
Neste capítulo, abordaremos outro recurso que são os relacionamentos inversos. É uma opção divertida que você verá na coleção que é inversamente igual a true e também confunde muitos desenvolvedores. Então, vamos falar sobre essa opção. Para entender isso, você realmente precisa pensar sobre o modelo relacional. Digamos que você tenha associações bidirecionais usando uma única chave estrangeira.
Do ponto de vista relacional, você tem uma chave estrangeira e ela representa o cliente para pedido e os pedidos para o cliente.
No modelo OO, você tem associações unidirecionais usando essas referências.
Não há nada que diga que duas associações unidirecionais representam a mesma associação bidirecional no banco de dados.
O problema aqui é que o NHibernate não tem informações suficientes para saber que customer.orders e order.customer representam o mesmo relacionamento no banco de dados.
Precisamos fornecer inverse equals true como uma dica, é porque as associações unidirecionais estão usando os mesmos dados.
Se tentarmos salvar esses relacionamentos que possuem 2 referências a eles, o NHibernate tentará atualizar essa referência duas vezes.
Na verdade, ele fará uma viagem extra de ida e volta ao banco de dados e também terá 2 atualizações para aquela chave estrangeira.
O inverso igual a verdadeiro informa ao NHibernate qual lado do relacionamento deve ser ignorado.
Quando você a aplica ao lado da coleção, o NHibernate sempre atualiza a chave estrangeira do outro lado, do lado do objeto filho.
Então, temos apenas uma atualização para essa chave estrangeira e não temos atualizações adicionais para esses dados.
Isso nos permite evitar essas atualizações duplicadas na chave estrangeira e também nos ajuda a prevenir violações de chave estrangeira.
Vamos dar uma olhada no customer.cs arquivo no qual você verá o AddOrdere a ideia aqui é que agora temos esse ponteiro de volta do pedido de volta ao cliente e ele precisa ser configurado. Portanto, quando um pedido é adicionado a um cliente, o ponteiro de retorno desse cliente é definido, caso contrário, seria nulo, portanto, precisamos disso para mantê-lo conectado corretamente no gráfico do objeto.
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
}
}
Aqui está o Program.cs implementação de arquivo.
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;
}
}
}
Ele vai salvar isso no banco de dados e, em seguida, recarregá-lo. Agora vamos executar seu aplicativo e abrir o NHibernate Profiler e ver como ele realmente o salvou.
Você notará que temos 3 grupos de declarações. O primeiro irá inserir o cliente, e o ID desse cliente é o Guid, que está destacado. A segunda instrução é inserida na tabela de pedidos.
Você notará que o mesmo Guia de Id do Cliente está definido lá, então tenha essa chave estrangeira definida. A última instrução é a atualização, que atualizará a chave estrangeira para o mesmo ID do cliente mais uma vez.
Agora o problema é que o cliente tem os pedidos, e os pedidos têm o cliente, não há como não termos dito ao NHibernate que na verdade é o mesmo relacionamento. A maneira como fazemos isso é com inverso igual a verdadeiro.
Então, vamos para o nosso customer.hbm.xml arquivo de mapeamento e defina o inverso igual a verdadeiro, conforme mostrado no código a seguir.
<?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>
Ao salvar os pedidos, ele definirá a chave estrangeira do lado do pedido. Agora, vamos executar este aplicativo novamente e abrir o perfilador NHibernate.
Se olharmos como eles são inseridos, obtemos a inserção no cliente e a inserção nos pedidos, mas não temos aquela atualização duplicada da chave estrangeira porque ela está sendo atualizada quando os pedidos estão sendo salvos.
Agora, você deve notar que se você tem apenas uma associação unidirecional e é o conjunto que está mantendo essa relação, então se você transformar o inverso em verdadeiro, aquela chave estrangeira nunca será definida e esses itens nunca terão seus chaves estrangeiras definidas no banco de dados.
Se você olhar para a relação muitos-para-um no Order.hbm.xml arquivo e você procura inverse, na verdade não tem um atributo inverse.
Ele sempre é definido a partir do item filho, mas se você tiver uma coleção muitos para muitos, poderá defini-la de qualquer lado.