NHibernate – 反向关系
NHibernate – 反向关系
在本章中,我们将介绍另一个特征,即反向关系。这是一个有趣的选项,您会在集合中看到与 true 成反比的选项,它也会让很多开发人员感到困惑。那么让我们来谈谈这个选项。要理解这一点,您真的必须考虑关系模型。假设您有一个使用单个外键的双向关联。
-
从关系的角度来看,您有一个外键,它代表客户到订单和订单到客户。
-
在 OO 模型中,您可以使用这些引用进行单向关联。
-
没有什么说两个单向关联在数据库中代表相同的双向关联。
-
这里的问题是 NHibernate 没有足够的信息来知道customer.orders和order.customer在数据库中代表相同的关系。
-
我们需要提供inverse equals true作为提示,这是因为单向关联使用相同的数据。
-
如果我们尝试保存这些有 2 个引用的关系,NHibernate 将尝试更新该引用两次。
-
它实际上会对数据库进行一次额外的往返,并且还会对该外键进行 2 次更新。
-
inverse 等于 true 告诉 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 组语句。第一个将插入客户,该客户的 ID 是 Guid,它突出显示。第二个语句是插入到订单表中。
您会注意到其中设置了相同的 Customer Id Guid,因此设置了该外键。最后一个语句是 update,它将再次将外键更新为相同的客户 ID。
现在的问题是客户有订单,订单有客户,我们不可能没有告诉NHibernate其实是同一种关系。我们这样做的方式是逆等于真。
因此,让我们转到我们的customer.hbm.xml映射文件,并将 inverse 设置为 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 分析器。
如果我们看看这些是如何插入的,我们会在客户中插入插入,并将插入到订单中,但是我们没有外键的重复更新,因为它在保存订单时正在更新。
-
现在,您应该注意,如果您只有一个单向关联并且它是维护这种关系的集合,那么如果您将 inverse 变为 true,则永远不会设置该外键,并且这些项目永远不会有它们的数据库中设置的外键。
-
如果您查看Order.hbm.xml文件中的多对一关系并查找 inverse,它实际上没有 inverse 属性。
-
它始终从子项设置,但如果您有一个多对多集合,则可以从任一侧设置它。