NHibernate - Relaciones inversas

En este capítulo, cubriremos otra característica que son las relaciones inversas. Es una opción divertida que verás en la colección que es inversamente igual a verdadero y también confunde a muchos desarrolladores. Así que hablemos de esta opción. Para entender esto, realmente debes pensar en el modelo relacional. Digamos que tiene asociaciones bidireccionales utilizando una única clave externa.

  • Desde un punto de vista relacional, tiene una clave externa, y representa tanto al cliente al pedido como a los pedidos al cliente.

  • Desde el modelo OO, tiene asociaciones unidireccionales utilizando estas referencias.

  • No hay nada que diga que dos asociaciones unidireccionales representan la misma asociación bidireccional en la base de datos.

  • El problema aquí es que NHibernate no tiene suficiente información para saber que customer.orders y order.customer representan la misma relación en la base de datos.

  • Necesitamos proporcionar inverse equals true como sugerencia, se debe a que las asociaciones unidireccionales están utilizando los mismos datos.

  • Si intentamos guardar estas relaciones que tienen 2 referencias a ellas, NHibernate intentará actualizar esa referencia dos veces.

  • En realidad, hará un viaje de ida y vuelta adicional a la base de datos y también tendrá 2 actualizaciones para esa clave externa.

  • La inversa es igual a verdadero le dice a NHibernate qué lado de la relación ignorar.

  • Cuando lo aplica al lado de la colección, NHibernate siempre actualizará la clave externa desde el otro lado, desde el lado del objeto secundario.

  • Entonces solo tenemos una actualización para esa clave externa y no tenemos actualizaciones adicionales para esos datos.

  • Esto nos permite evitar estas actualizaciones duplicadas de la clave externa y también nos ayuda a prevenir violaciones de la clave externa.

Echemos un vistazo al customer.cs archivo en el que verá el AddOrdery la idea aquí es que ahora tenemos este puntero de retroceso desde el pedido hasta el cliente y debe configurarse. Entonces, cuando se agrega un pedido a un cliente, se establece el puntero de retroceso de ese cliente; de ​​lo contrario, sería nulo, por lo que necesitamos esto para mantener esto conectado correctamente en el gráfico de objetos.

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 
   } 
}

Aquí está el Program.cs implementación de archivos.

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 guardará en la base de datos y luego lo volverá a cargar. Ahora ejecutemos su aplicación y abramos NHibernate Profiler y veamos cómo realmente la guardó.

Notarás que tenemos 3 grupos de declaraciones. El primero insertará al cliente, y el ID de ese cliente es el Guid, que está resaltado. La segunda declaración se inserta en la tabla de pedidos.

Notará que la misma Guía de identificación de cliente está configurada allí, así que tenga configurada esa clave externa. La última declaración es la actualización, que actualizará la clave externa al mismo ID de cliente una vez más.

Ahora el problema es que el cliente tiene los pedidos y los pedidos tienen el cliente, no hay forma de que no le hayamos dicho a NHibernate que en realidad es la misma relación. La forma en que hacemos esto es con inversa igual a verdadero.

Así que vayamos a nuestro customer.hbm.xml archivo de mapeo y establezca el inverso igual a verdadero como se muestra en el siguiente código.

<?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>

Al guardar los pedidos, establecerá esa clave externa desde el lado del pedido. Ahora ejecutemos esta aplicación nuevamente y abramos el generador de perfiles NHibernate.

Si miramos cómo se insertan, obtenemos la inserción en el cliente y la inserción en los pedidos, pero no tenemos esa actualización duplicada de la clave externa porque se actualiza cuando se guardan los pedidos.

  • Ahora, debe tener en cuenta que si solo tiene una asociación unidireccional y es el conjunto el que mantiene esta relación, entonces si cambia el inverso es igual a verdadero, esa clave externa nunca se establecerá y esos elementos nunca tendrán su claves externas establecidas en la base de datos.

  • Si observa la relación de muchos a uno en el Order.hbm.xml archivo y busca inverso, en realidad no tiene un atributo inverso.

  • Siempre se configura desde el elemento secundario, pero si tiene una colección de varios a muchos, puede configurarlo desde cualquier lado.