- NHibernate 教程
- NHibernate - 首页
- NHibernate - 概述
- NHibernate - 架构
- NHibernate - ORM
- NHibernate - 环境设置
- NHibernate - 入门
- NHibernate - 基本 ORM
- NHibernate - 基本 CRUD 操作
- NHibernate - Profiler
- 为映射文件添加 Intellisense
- NHibernate - 数据类型映射
- NHibernate - 配置
- NHibernate - 覆盖配置
- NHibernate - 批量大小
- NHibernate - 缓存
- NHibernate - 映射组件
- NHibernate - 关系
- NHibernate - 集合映射
- NHibernate - 级联操作
- NHibernate - 延迟加载
- NHibernate - 反向关系
- NHibernate - Load/Get
- NHibernate - LINQ
- NHibernate - 查询语言
- NHibernate - Criteria 查询
- NHibernate - QueryOver 查询
- NHibernate - 原生 SQL
- NHibernate - Fluent Hibernate
- NHibernate 有用资源
- NHibernate - 快速指南
- NHibernate - 有用资源
- NHibernate - 讨论
NHibernate - 反向关系
在本章中,我们将介绍另一个功能,即反向关系。这是一个有趣的选项,您将在集合上看到它,这些集合的反向等于 true,并且它也让许多开发人员感到困惑。所以让我们来谈谈这个选项。要理解这一点,您确实需要考虑关系模型。假设您使用单个外键进行双向关联。
从关系的角度来看,您有一个外键,它表示客户到订单和订单到客户。
从 OO 模型来看,您使用这些引用进行单向关联。
没有任何东西表明两个单向关联在数据库中表示相同的双向关联。
这里的问题是 NHibernate 没有足够的信息来知道customer.orders 和order.customer 在数据库中表示相同的关系。
我们需要提供inverse equals true 作为提示,这是因为单向关联使用相同的数据。
如果我们尝试保存这些有两个引用指向它们的关系,NHibernate 将尝试更新该引用两次。
它实际上会对数据库进行额外的往返,并且它还将对该外键进行两次更新。
inverse equals true 告诉 NHibernate 忽略关系的哪一侧。
当您将其应用于集合侧时,NHibernate 将始终从另一侧,即从子对象侧更新外键。
然后我们只对该外键进行一次更新,并且我们没有对该数据进行额外的更新。
这使我们能够防止对外键进行重复更新,并且还有助于我们防止外键冲突。
让我们看一下customer.cs 文件,您将在其中看到AddOrder 方法,这里的想法是我们现在有了从订单返回到客户的反向指针,并且需要设置它。因此,当订单添加到客户时,该客户的反向指针被设置,否则它将为 null,因此我们需要这样做才能将对象图中这些连接正确地连接在一起。
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,已突出显示。第二个语句是插入 orders 表。
您会注意到相同的 Customer Id Guid 在其中设置,因此设置了该外键。最后一个语句是更新,它将再次将外键更新为相同的客户 ID。
现在问题是客户拥有订单,订单拥有客户,我们没有告诉 NHibernate 它实际上是相同的关系。我们使用 inverse equals true 来做到这一点。
因此,让我们转到我们的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 Profiler。
如果我们查看这些是如何插入的,我们会得到客户的插入,以及订单的插入,但我们没有重复更新外键,因为在保存订单时正在更新它。
现在,您应该注意,如果您只有一个单向关联,并且是正在维护此关系的集合,那么如果您将 inverse equals true,则永远不会设置外键,并且这些项目永远不会在数据库中设置其外键。
如果您查看Order.hbm.xml 文件中的多对一关系,并查找 inverse,它实际上没有 inverse 属性。
它始终从子项设置,但如果您有多对多集合,则可以从任一侧设置它。