NHibernate - Mối quan hệ nghịch đảo

Trong chương này, chúng ta sẽ đề cập đến một tính năng khác là Mối quan hệ nghịch đảo. Đó là một tùy chọn thú vị mà bạn sẽ thấy trên bộ sưu tập tỷ lệ nghịch với true và nó cũng khiến nhiều nhà phát triển bối rối. Vì vậy, chúng ta hãy nói về tùy chọn này. Để hiểu điều này, bạn thực sự phải nghĩ về mô hình quan hệ. Giả sử bạn có một liên kết hai chiều bằng cách sử dụng một khóa ngoại duy nhất.

  • Từ quan điểm quan hệ, bạn có một khóa ngoại và nó đại diện cho cả khách hàng đặt hàng và đặt hàng cho khách hàng.

  • Từ mô hình OO, bạn có các liên kết đơn hướng bằng cách sử dụng các tham chiếu này.

  • Không có gì nói rằng hai liên kết đơn hướng đại diện cho cùng một liên kết hai chiều trong cơ sở dữ liệu.

  • Vấn đề ở đây là NHibernate không có đủ thông tin để biết rằng customer.ordersorder.customer đại diện cho mối quan hệ giống nhau trong cơ sở dữ liệu.

  • Chúng tôi cần cung cấp inverse equals true như một gợi ý, đó là bởi vì các liên kết đơn hướng đang sử dụng cùng một dữ liệu.

  • Nếu chúng tôi cố gắng lưu các mối quan hệ có 2 tham chiếu đến chúng, NHibernate sẽ cố gắng cập nhật tham chiếu đó hai lần.

  • Nó thực sự sẽ thực hiện thêm một vòng đối với cơ sở dữ liệu và nó cũng sẽ có 2 bản cập nhật cho khóa ngoại đó.

  • Giá trị nghịch đảo bằng true cho NHibernate biết nên bỏ qua mặt nào của mối quan hệ.

  • Khi bạn áp dụng nó cho phía thu thập và NHibernate sẽ luôn cập nhật khóa ngoại từ phía bên kia, từ phía đối tượng con.

  • Sau đó, chúng tôi chỉ có một bản cập nhật cho khóa ngoại đó và chúng tôi không có bản cập nhật bổ sung cho dữ liệu đó.

  • Điều này cho phép chúng tôi ngăn chặn các bản cập nhật trùng lặp đối với khóa ngoại và nó cũng giúp chúng tôi ngăn chặn các vi phạm khóa ngoại.

Chúng ta hãy nhìn vào customer.cs trong đó bạn sẽ thấy AddOrdervà ý tưởng ở đây là bây giờ chúng ta có con trỏ ngược này từ đơn đặt hàng trở lại khách hàng và nó cần được thiết lập. Vì vậy, khi một đơn đặt hàng được thêm vào một khách hàng, con trỏ quay lại của khách hàng đó sẽ được đặt, nếu không, nó sẽ là null, vì vậy chúng ta cần điều này để giữ cho điều này được kết nối đúng cách với nhau trong biểu đồ đối tượng.

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

Đây là Program.cs triển khai tệp.

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

Nó sẽ lưu nó vào cơ sở dữ liệu và sau đó tải lại nó. Bây giờ, hãy chạy ứng dụng của bạn và mở NHibernate Profiler và xem nó đã thực sự lưu nó như thế nào.

Bạn sẽ nhận thấy rằng chúng tôi có 3 nhóm câu lệnh. Đầu tiên sẽ chèn khách hàng và ID của khách hàng đó là Hướng dẫn, được đánh dấu. Câu lệnh thứ hai được chèn vào bảng đơn hàng.

Bạn sẽ nhận thấy cùng một Hướng dẫn ID khách hàng được đặt trong đó, vì vậy hãy đặt khóa ngoại đó. Câu lệnh cuối cùng là bản cập nhật, sẽ cập nhật lại khóa ngoại cho cùng một id khách hàng.

Bây giờ vấn đề là khách hàng có đơn đặt hàng, và đơn đặt hàng có khách hàng, không có cách nào mà chúng tôi đã không nói với NHibernate rằng nó thực sự là cùng một mối quan hệ. Cách chúng tôi làm điều này là với nghịch đảo bằng đúng.

Vì vậy, chúng ta hãy đi đến customer.hbm.xml ánh xạ tệp và đặt nghịch đảo bằng true như được hiển thị trong đoạn mã sau.

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

Khi lưu các lệnh, nó sẽ thiết lập khóa ngoại đó từ phía đơn hàng. Bây giờ hãy chạy lại ứng dụng này và mở hồ sơ NHibernate.

Nếu chúng ta nhìn vào cách chúng được chèn, chúng ta nhận được đoạn chèn trong khách hàng và chèn vào đơn đặt hàng, nhưng chúng tôi không có bản cập nhật trùng lặp của khóa ngoại vì nó được cập nhật khi đơn đặt hàng đang được lưu.

  • Bây giờ, bạn nên lưu ý rằng nếu bạn chỉ có một liên kết đơn hướng và đó là tập hợp đang duy trì mối quan hệ này, thì nếu bạn biến nghịch đảo bằng true, khóa ngoại đó sẽ không bao giờ được đặt và những mục đó sẽ không bao giờ có khóa ngoại được đặt trong cơ sở dữ liệu.

  • Nếu bạn nhìn vào mối quan hệ nhiều-một trong Order.hbm.xml và bạn tìm kiếm nghịch đảo, nó thực sự không có thuộc tính nghịch đảo.

  • Nó luôn được đặt từ mục con, nhưng nếu bạn có một bộ sưu tập nhiều đến nhiều, bạn có thể đặt nó từ một trong hai bên.