NHibernate - ความสัมพันธ์ผกผัน
ในบทนี้เราจะกล่าวถึงคุณลักษณะอื่นซึ่งก็คือความสัมพันธ์แบบผกผัน เป็นตัวเลือกที่น่าขบขันที่คุณจะเห็นในคอลเลกชันที่ผกผันเท่ากับจริงและยังสร้างความสับสนให้กับนักพัฒนาจำนวนมาก เรามาพูดถึงตัวเลือกนี้กัน เพื่อให้เข้าใจสิ่งนี้คุณต้องคิดถึงโมเดลเชิงสัมพันธ์จริงๆ สมมติว่าคุณมีการเชื่อมโยงแบบสองทิศทางโดยใช้คีย์ต่างประเทศเดียว
จากมุมมองเชิงสัมพันธ์คุณมีคีย์ต่างประเทศ 1 รายการและเป็นตัวแทนของลูกค้าที่สั่งซื้อและสั่งซื้อให้กับลูกค้า
จากแบบจำลอง OO คุณมีการเชื่อมโยงแบบทิศทางเดียวโดยใช้การอ้างอิงเหล่านี้
ไม่มีสิ่งใดที่บอกว่าการเชื่อมโยงสองทิศทางเดียวแสดงถึงการเชื่อมโยงแบบสองทิศทางเดียวกันในฐานข้อมูล
ปัญหาคือ NHibernate ไม่มีข้อมูลเพียงพอที่จะทราบได้ customer.orders และ order.customer แสดงถึงความสัมพันธ์เดียวกันในฐานข้อมูล
เราจำเป็นต้องให้ inverse equals true เป็นคำใบ้เป็นเพราะการเชื่อมโยงทิศทางเดียวใช้ข้อมูลเดียวกัน
หากเราพยายามบันทึกความสัมพันธ์เหล่านี้ที่มีการอ้างอิงถึง 2 รายการ NHibernate จะพยายามอัปเดตการอ้างอิงนั้นสองครั้ง
มันจะทำพิเศษไปกลับฐานข้อมูลและจะมีการอัปเดต 2 คีย์ต่างประเทศนั้น
ค่าผกผันเท่ากับจริงจะบอกให้ NHibernate มองข้ามความสัมพันธ์ด้านใดไป
เมื่อคุณนำไปใช้กับด้านคอลเลกชันและ NHibernate จะอัปเดตคีย์จากอีกด้านหนึ่งเสมอจากด้านวัตถุลูก
จากนั้นเราจะมีการอัปเดตคีย์นอกนั้นเพียงครั้งเดียวและเราไม่มีการอัปเดตเพิ่มเติมสำหรับข้อมูลนั้น
สิ่งนี้ช่วยให้เราสามารถป้องกันการอัปเดตคีย์ภายนอกที่ซ้ำกันเหล่านี้และยังช่วยป้องกันการละเมิดคีย์ต่างประเทศ
ลองดูที่ไฟล์ customer.cs ซึ่งคุณจะเห็นไฟล์ AddOrderวิธีการและแนวคิดคือตอนนี้เรามีตัวชี้ย้อนกลับจากคำสั่งซื้อกลับไปยังลูกค้าและจำเป็นต้องตั้งค่า ดังนั้นเมื่อมีการเพิ่มคำสั่งซื้อให้กับลูกค้าตัวชี้หลังของลูกค้านั้นจะถูกตั้งค่ามิฉะนั้นจะเป็นโมฆะดังนั้นเราจึงต้องการสิ่งนี้เพื่อให้สิ่งนี้เชื่อมต่อเข้าด้วยกันอย่างเหมาะสมในกราฟวัตถุ
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
}
}
ที่นี่คือ Program.cs การใช้งานไฟล์
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;
}
}
}
มันจะบันทึกลงในฐานข้อมูลแล้วโหลดใหม่ ตอนนี้ให้เรียกใช้แอปพลิเคชันของคุณและเปิด NHibernate Profiler และดูว่ามันบันทึกได้อย่างไร
คุณจะสังเกตได้ว่าเรามีข้อความ 3 กลุ่ม คนแรกจะแทรกลูกค้าและรหัสลูกค้าคือ Guid ซึ่งไฮไลต์ไว้ คำสั่งที่สองถูกแทรกลงในตารางคำสั่งซื้อ
คุณจะสังเกตเห็นคู่มือรหัสลูกค้าเดียวกันตั้งอยู่ที่นั่นดังนั้นให้ตั้งค่าคีย์ต่างประเทศนั้น คำสั่งสุดท้ายคือการอัปเดตซึ่งจะอัปเดตคีย์นอกเป็นรหัสลูกค้าเดิมอีกครั้ง
ตอนนี้ปัญหาคือลูกค้ามีคำสั่งซื้อและคำสั่งซื้อมีลูกค้าไม่มีทางที่เราไม่ได้บอก NHibernate ว่ามันเป็นความสัมพันธ์เดียวกัน วิธีที่เราทำคือผกผันเท่ากับจริง
ไปที่ customer.hbm.xml การแมปไฟล์และตั้งค่าผกผันให้เท่ากับ true ดังแสดงในโค้ดต่อไปนี้
<?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>
เมื่อบันทึกคำสั่งซื้อจะตั้งค่าคีย์ต่างประเทศนั้นจากด้านคำสั่งซื้อ ตอนนี้ให้เรียกใช้แอปพลิเคชันนี้อีกครั้งและเปิดตัวสร้างโปรไฟล์ NHibernate
หากเราดูว่ามีการแทรกข้อมูลเหล่านั้นอย่างไรเราจะได้รับการแทรกในลูกค้าและการแทรกลงในคำสั่งซื้อ แต่เราไม่มีการอัปเดตคีย์ภายนอกที่ซ้ำกันเนื่องจากมีการอัปเดตเมื่อมีการบันทึกคำสั่งซื้อ
ตอนนี้คุณควรทราบว่าหากคุณมีเพียงการเชื่อมโยงแบบทิศทางเดียวและเป็นชุดที่รักษาความสัมพันธ์นี้ถ้าคุณเปลี่ยนผกผันเท่ากับจริงคีย์ต่างประเทศนั้นจะไม่ถูกตั้งค่าและรายการเหล่านั้นจะไม่มีทางมี คีย์ต่างประเทศที่ตั้งค่าในฐานข้อมูล
หากคุณดูความสัมพันธ์แบบกลุ่มต่อหนึ่งในไฟล์ Order.hbm.xml และคุณมองหาผกผันมันไม่มีแอตทริบิวต์ผกผัน
จะตั้งค่าจากรายการย่อยเสมอ แต่ถ้าคุณมีคอลเล็กชันแบบกลุ่มต่อกลุ่มคุณสามารถตั้งค่าได้จากด้านใดด้านหนึ่ง