NHibernate - Relacje

W tym rozdziale przyjrzymy się relacjom w NHibernate. Zwróćmy uwagę na to, jak możemy zrozumieć relacje w NHibernate. Najłatwiej jest pomyśleć o relacjach z perspektywy bazy danych.

  • Najpierw stworzymy nową aplikację, w której stworzymy pewne relacje między klientem a podmiotami zamawiającymi.

  • Pierwsza relacja, której przyjrzymy się, to klasyczny związek kolekcji.

  • Mamy klienta z kompletem zamówień.

  • Jest to relacja jeden do wielu i jest reprezentowana w bazie danych przez 2 tabele, aw tabeli zamówień znajduje się identyfikator klienta i mamy relację z kluczem obcym z powrotem do klienta.

Najpierw musimy stworzyć bazę danych oraz dwie tabele Customer and Order. Możesz to utworzyć, określając następującą kwerendę w Eksploratorze programu SQL Server.

USE [master] 
GO 
CREATE DATABASE [NHibernateDemo] 
GO 
USE [NHibernateDemo] 
GO

CREATE TABLE [dbo].[Customer]( 
   [Id] [uniqueidentifier] NOT NULL, 
   [FirstName] [nvarchar](100) NOT NULL, 
   [LastName] [nvarchar](100) NOT NULL, 
   [Points] [int] NULL, [HasGoldStatus] [bit] NULL, 
   [MemberSince] [date] NULL, 
   [CreditRating] [nchar](20) NULL, 
   [AverageRating] [decimal](18, 4) NULL, 
   [Street] [nvarchar](100) NULL, 
   [City] [nvarchar](100) NULL, 
   [Province] [nvarchar](100) NULL, 
   [Country] [nvarchar](100) NULL,
   PRIMARY KEY CLUSTERED ([Id] ASC) 
) 

GO 
CREATE TABLE [dbo].[Order]( 
   [Id] [uniqueidentifier] NOT NULL, 
   [CustomerId] [uniqueidentifier] NULL, 
   [Ordered] [datetime] NULL, 
   [Shipped] [datetime] NULL, 
   [Street] [nvarchar](100) NULL, 
   [City] [nvarchar](100) NULL, 
   [Province] [nvarchar](100) NULL, 
   [Country] [nvarchar](100) NULL, 
   PRIMARY KEY CLUSTERED ([Id] ASC) 
) 
GO

Stworzy dwie tabele w bazie danych. Poniższy obraz przedstawia tabelę klientów.

Poniższy obraz przedstawia tabelę zamówień, w której można zobaczyć relację klucza obcego z powrotem do klienta.

Musimy zdefiniować parametry połączenia w app.config plik, tutaj jest implementacja pliku app.config.

<?xml version = "1.0" encoding = "utf-8" ?> 
<configuration> 
   
   <connectionStrings> 
      <add name = "default" connectionString = "Data Source =
         (localdb)\MSSQLLocalDB;Initial Catalog = NHibernateDemo;Integrated Security =
         True;Connect Timeout = 30;Encrypt = False;TrustServerCertificate = False;
         ApplicationIntent = ReadWrite;MultiSubnetFailover = False"/> 
   </connectionStrings> 

</configuration>

Aby zainstalować NHibernate w aplikacji, uruchom następujące polecenie w oknie konsoli NuGet Manager.

install-package NHibernate

Aby skonfigurować konfigurację NHibernate, musimy zdefiniować konfigurację w hibernate.cfg.xml plik, jak pokazano w poniższym kodzie.

<xml version = "1.0" encoding = "utf-8" ?> 
<hibernate-configuration xmlns = "urn:nhibernate-configuration-2.2"> 
   
   <session-factory> 
      <property name = "connection.connection_string_name">default</property>
		
      <property name = "connection.driver_class">
         NHibernate.Driver.SqlClientDriver 
      </property> 
		
      <property name = "dialect">
         NHibernate.Dialect.MsSql2008Dialect
      </property> 
		
      <property name = "show_sql">true</property> 
   </session-factory> 

</hibernate-configuration>

W tym przykładzie będziemy pracować z dwiema klasami domeny, Klientem i Zamówieniem.

Oto implementacja pliku Customer.cs, w której mamy dwie klasy, jedna to klasa Customer, a druga to klasa Location, w której obiekt jest używany jako adres w klasie Customer.

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

Oto plik mapowania Customer.hbm.xml w którym klasa Customer jest mapowana na tabelę Customer.

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

   </class> 
</hibernate-mapping>

Mamy również klasę zamówienia i tutaj jest implementacja Order.cs plik.

using System; using Iesi.Collections.Generic;

namespace NHibernateDemo { 

   public class Order { 
      
      public virtual Guid Id { get; set; } 
      public virtual DateTime Ordered { get; set; } 
      public virtual DateTime? Shipped { get; set; }
      public virtual Location ShipTo { get; set; } 
      public virtual Customer Customer { get; set; }
      
      public override string ToString() { 
         return string.Format("Order Id: {0}", Id); 
      } 
   } 
}

Relacja wiele do jednego

Musimy również zmapować klasę Order na tabelę Order w bazie danych, więc tutaj jest implementacja Order.hbm.xml plik.

<?xml version = "1.0" encoding = "utf-8" ?> 
<hibernate-mapping xmlns = "urn:nhibernate-mapping-2.2" assembly = "NHibernateDemo" 
   namespace = "NHibernateDemo"> 

   <class name = "Order" table = "`Order`"> 
   
      <id name = "Id"> 
         <generator class = "guid.comb"/> 
      </id> 
		
      <property name = "Ordered"/> 
      <property name = "Shipped"/> 
   
      <component name = "ShipTo"> 
         <property name = "Street"/> 
         <property name = "City"/> 
         <property name = "Province"/> 
         <property name = "Country"/> 
      </component> 

      <!--<many-to-one name = "Customer" column = "CustomerId" cascade =
         "save-update"/>--> 
			
   </class> 
</hibernate-mapping>

Relacja jeden do wielu

W tym miejscu przyjrzymy się relacji jeden do wielu, w tym przypadku między klientem a zamówieniami. Mamy tutaj naszego klienta, tworzymy nowego i widać, że kolekcja jest inicjowana następującą parą zamówień.

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

Stworzymy więc nowego klienta i następnie go zapiszemy, po zapisaniu znajdziemy ID i przeładujemy go w kolejnej sesji w metodzie Main, jak pokazano w poniższym programie.

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 reloaded = session.Load<Customer>(id); 
      Console.WriteLine("Reloaded:"); 
      Console.WriteLine(reloaded); 
      tx.Commit(); 
   }
   
   Console.WriteLine("Press <ENTER> to exit..."); 
   Console.ReadLine(); 
}

Oto kompletny Program.cs implementacja plików.

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 reloaded = session.Load<Customer>(id); 
            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 =&ht; { 
            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; 
      } 
   } 
}

Po uruchomieniu tej aplikacji zobaczysz następujące dane wyjściowe.

New Customer:

John Doe (00000000-0000-0000-0000-000000000000)
   Points: 100
   HasGoldStatus: True
   MemberSince: 1/1/2012 12:00:00 AM (Unspecified)
   CreditRating: Good
   AverageRating: 42.42424242

   Orders:
      Order Id: 00000000-0000-0000-0000-000000000000
      Order Id: 00000000-0000-0000-0000-000000000000

Reloaded:
John Doe (9b0fcf10-83f6-4f39-bda5-a5b800ede2ba)
   Points: 100
   HasGoldStatus: True
   MemberSince: 1/1/2012 12:00:00 AM (Utc)
   CreditRating: Good
   AverageRating: 42.4242

   Orders:
Press <ENTER> to exit...

Jak widać, początkowo klient ma 2 zamówienia, ale kiedy go przeładujemy, nie ma żadnych zamówień do wyświetlenia. Jeśli spojrzysz nacustomer.hbm.xmlwidać tutaj, że nie odwzorowujemy faktycznej kolekcji zamówień. Więc NHibernate nic o tym nie wie. Przejdźmy dalej i dodajmy to.

<?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`"> 
         <key column = "CustomerId"/> 
         <one-to-many class = "Order"/> 
      </set> 
   
   </class> 
</hibernate-mapping>

To jest zestaw, a nazwa tej kolekcji to „Zamówienia”, która jest przechowywana w tabeli o nazwie order. Musimy podać klucz, który jest nazwą klucza obcego lub znaleźć zamówienia. Te zamówienia są identyfikowane lub należą do klienta za pomocą identyfikatora klienta. A potem muszę zauważyć, że jest to relacja jeden do wielu i dotyczy ona klasy zamówienia.

Musimy również nieco zmienić metodę Main, zapisując nowe zamówienia klientów do bazy danych, jak również pokazano w poniższym programie.

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); 
      
      foreach (var order in newCustomer.Orders) { 
         session.Save(order); 
      } 
		
      id = newCustomer.Id; 
      tx.Commit(); 
   }
   
   using(var session = sessionFactory.OpenSession()) 
	
   using(var tx = session.BeginTransaction()) { 
      var reloaded = session.Load<Customer>(id); 
      Console.WriteLine("The orders were ordered by: "); 
      
      foreach (var order in reloaded.Orders) { 
         Console.WriteLine(order.Customer); 
      } 
		
      tx.Commit(); 
   }
   Console.WriteLine("Press <ENTER> to exit..."); Console.ReadLine();
}

Określiliśmy również, który klient zamówił ten konkretny produkt. Musimy więc stworzyć relację typu „wiele do jednego”, aby powiązać to zamówienie z powrotem do tego klienta.

Więc przejdźmy do Order.hbm.xml plik i dodaj wiele do jednego, a następnie nazwij pole klienta i kolumnę za pomocą identyfikatora klienta.

<?xml version = "1.0" encoding = "utf-8" ?> 
<hibernate-mapping xmlns = "urn:nhibernate-mapping-2.2" assembly = "NHibernateDemo"
   namespace = "NHibernateDemo"> 
	
   <class name = "Order" table = "`Order`">
      
      <id name = "Id"> 
         <generator class = "guid.comb"/> 
      </id>
      
      <property name = "Ordered"/> 
      <property name = "Shipped"/> 
      
      <component name = "ShipTo"> 
         <property name = "Street"/> 
         <property name = "City"/> 
         <property name = "Province"/> 
         <property name = "Country"/> 
      </component> 

      <many-to-one name = "Customer" column = "CustomerId"/> 
		
   </class> 
</hibernate-mapping>

Uruchommy ponownie tę aplikację, a teraz zobaczysz następujące dane wyjściowe.

New Customer:
John Doe (00000000-0000-0000-0000-000000000000)
   Points: 100
   HasGoldStatus: True
   MemberSince: 1/1/2012 12:00:00 AM (Unspecified)
   CreditRating: Good
   AverageRating: 42.42424242

   Orders:
      Order Id: 00000000-0000-0000-0000-000000000000
      Order Id: 00000000-0000-0000-0000-000000000000

Reloaded:
John Doe (660a6f29-650e-4380-99e0-a5b800febbde)
   Points: 100
   HasGoldStatus: True
   MemberSince: 1/1/2012 12:00:00 AM (Utc)
   CreditRating: Good
   AverageRating: 42.4242

   Orders:
      Order Id: 57314deb-e023-4e55-ac1e-a5b800febbe3
      Order Id: fc065683-d5f5-484b-ae42-a5b800febbe3

The orders were ordered by:
John Doe (660a6f29-650e-4380-99e0-a5b800febbde)
   Points: 100
   HasGoldStatus: True
   MemberSince: 1/1/2012 12:00:00 AM (Utc)
   CreditRating: Good
   AverageRating: 42.4242

   Orders:
      Order Id: 57314deb-e023-4e55-ac1e-a5b800febbe3
      Order Id: fc065683-d5f5-484b-ae42-a5b800febbe3

John Doe (660a6f29-650e-4380-99e0-a5b800febbde)
   Points: 100
   HasGoldStatus: True
   MemberSince: 1/1/2012 12:00:00 AM (Utc)
   CreditRating: Good
   AverageRating: 42.4242

   Orders:
      Order Id: 57314deb-e023-4e55-ac1e-a5b800febbe3
      Order Id: fc065683-d5f5-484b-ae42-a5b800febbe3
		
Press <ENTER> to exit...