NHibernate - 快速指南
NHibernate - 概述
在本章中,我们将讨论 NHibernate 是什么、它可以在哪些平台上实现、它的优点是什么以及与之相关的其他方面。
什么是 NHibernate?
NHibernate 是一个成熟的开源 .NET 框架对象关系映射器。它开发活跃、功能齐全,并已在数千个成功项目中使用。它建立在 ADO.NET 之上,当前版本为 NHibernate 4.0.4。
NHibernate 是一个开源 .NET 对象关系映射器,根据 GNU 宽通用公共许可证分发。
它基于 Hibernate,后者是一种流行的 Java 对象关系映射器,具有非常成熟和活跃的代码库。
它提供了一个将面向对象域模型映射到传统关系数据库的框架。
NHibernate 由 Tom Barrett 发起,该项目自 2003 年 2 月以来一直存在,这是他们第一次提交。
这是一个大项目,提供了很多功能。
有一个可用的 NuGet 包,这使得将其添加到项目中非常容易。
为什么是 NHibernate?
现在的问题是为什么我们需要对象关系映射器?这是因为对象世界和关系世界之间存在脱节。
在对象世界中,一切都围绕对象;我们将拥有我们数据的事物称为对象。
关系世界都是基于集合的,我们处理的表和行与对象世界不同。
在对象世界中,我们有单向关联。如果客户有一个指向订单的指针,这并不一定意味着订单有一个指向客户的指针,它可能有也可能没有。
在关系世界中,所有关联都是双向的,并且可以通过外键来实现。
所有关联本质上都是双向的,因此当我们处理对象关系映射时,我们也需要处理这种脱节。
在对象世界中,我们使用单向的指针,而在关系世界中,我们拥有本质上是双向的外键。
对象世界有继承的概念,其中车辆可以有许多不同的子类,因此汽车是一种车辆,船是一种车辆,跑车是一种汽车,这些类型的继承关系。
关系世界没有有继承的概念。
映射
那么我们如何映射所有这些不相交的关系?映射的概念来自对象关系映射器。如下图所示,主要有三件事需要理解。

在您的应用程序中,您将需要类定义,通常是 C# 代码及其代表我们类的 .NET 代码,例如 Employee 类、Customer 类、Order 类等。
在底部,您可以看到数据库模式,这是我们在关系数据库中的数据定义语言,它指定了客户表的样子,员工表的样子。
在这两者之间,我们有映射元数据,它告诉对象关系映射器如何从 C# 中的对象世界转换为数据库世界(就行和列以及外键关系而言)。
这种映射元数据可以用多种不同的方式表示,我们将研究其中的多种不同方式。在 NHibernate 应用程序中很常见。
它由 HBM (Hibernate Mapping) 文件表示,这些文件是 XML 文件。
支持的数据库
NHibernate 支持各种不同的数据库。任何现有的关系数据库都可以访问 NHibernate。
SQL 服务器是主要支持的数据库,这是大多数开发人员在开发过程中使用的,它可能是最常见的数据库。
它还与 Oracle配合得很好。
它还支持 DB2、Firebird、MySQL、PostgreSQL、SQL Lite
它还有 ODBC 和 OLEDB 驱动程序。
NHibernate - 架构
如今,许多系统都采用分层架构设计,NHibernate 也采用这种架构,并且与该设计完美配合。
分层架构
分层架构将系统划分为多个组,每个组包含针对特定问题领域的代码,这些组称为层。大多数企业级应用程序使用由三层组成的高级应用程序架构 −
- 表示层
- 业务层
- 持久层

例如,用户界面层(也称为表示层)可能包含用于构建网页和处理用户输入的所有应用程序代码。
分层方法的一个主要优点是,您通常可以对一个层进行更改而不会对其他层造成任何重大干扰,从而使系统更不易损坏且更易于维护。
表示层
它是最顶层,包含负责绘制用户界面、页面的代码,对话框或屏幕,收集用户输入并控制导航。
业务层
业务层负责实现用户作为问题域的一部分会理解的任何业务规则或系统要求。
它还重用了持久层定义的模型。
持久层
持久层由负责保存和检索应用程序数据的类和组件组成。
此层还定义了模型类和数据库之间的映射。 NHibernate 主要用于此层。
数据库
- 数据库存在于 .NET 应用程序之外。
- 它是系统状态的实际、持久表示。
- 如果使用 SQL 数据库,则数据库包括关系模式和可能的存储过程。
帮助程序/实用程序类
每个应用程序都有一组支持其他层的帮助程序或实用程序类:例如,UI 小部件、消息传递类、异常类和日志记录实用程序。
这些元素不被视为层,因为它们不遵守分层架构中层间依赖关系的规则。
NHibernate 架构
它是 NHibernate 应用程序的高级视图,您还可以看到简单的 NHibernate 架构。

应用程序代码使用 NHibernate ISession 和 IQuery API 进行持久性操作,并且只需管理数据库事务,理想情况下使用 NHibernate ITransaction API。
NHibernate - ORM
在真正开始使用 NHibernate 之前,我们需要了解其构建的基础。NHibernate 是一种基于对象关系映射或 ORM 思想的持久性技术。
什么是 ORM?
对象关系映射 (ORM) 是一种编程技术,用于在面向对象编程语言中不兼容的类型系统之间转换数据。换句话说,它是将应用程序的业务对象映射到关系数据库表的概念,以便可以通过应用程序的对象模型轻松访问和更新数据。
正如您所知,关系数据库提供了一种很好的数据存储方式,而面向对象编程是构建复杂应用程序的好方法。
NHibernate 和 ORM 通常与具有非平凡业务逻辑、域模型和某种数据库的应用程序最相关。
使用 ORM,可以非常轻松地创建一个转换层,该转换层可以轻松地将对象转换为关系数据并再次转换回来。
首字母缩略词 ORM 也可以表示对象角色建模,这个术语是在对象/关系映射变得重要之前发明的。
它描述了一种用于数据库建模的信息分析方法。
为什么ORM?
ORM 是一个框架,它使您可以将面向对象语言中的对象世界映射到关系数据库中的关系表中的行。
为了理解这个概念,让我们看一下下面的图表。

在上图中,您可以看到右侧有一个名为 Employee 的表,其中包含与单个员工相关的每条数据的列。
我们有一个用于唯一标识每个员工的 Id 列。
一列用于员工姓名,另一列用于员工入职日期,最后一列用于员工年龄。
如果我们想编写一些代码来将新员工存储在表中,它并不是那么容易。
在上图中,您还可以看到我们有一个员工对象,其中包含 Id、姓名、入职日期和年龄等字段。
如果没有 ORM,我们必须将此对象转换为几个不同的 SQL 语句,这些语句会将员工数据插入员工表中。
因此,编写代码来创建 SQL 以执行上述场景并不难,但有点乏味,而且很容易出错。
使用像 NHibernate 这样的 ORM,我们可以声明某些类应该如何映射到关系表,并让 ORM 或 NHibernate 处理创建 SQL 以插入、更新、删除查询员工表中数据的繁琐工作。
这使我们能够将代码专注于使用对象,并让这些对象自动转换为关系表。
所以ORM 的真正作用是让我们免于手动将对象映射到表。
NHibernate - 环境设置
要开始使用 NHibernate,我们需要 Visual Studio 和 NHibernate 包。
Visual Studio 安装
Microsoft 提供了 Visual Studio 的免费版,其中还包含SQL Server,可从https://www.visualstudio.com 下载。以下是安装步骤。
步骤 1 −下载完成后,运行安装程序,将显示以下对话框。

步骤 2 − 单击"安装"按钮,它将开始安装过程。

步骤 3 − 安装过程成功完成后,您将看到以下对话框。

步骤 4 − 关闭此对话框并重新启动计算机(如果需要)。
步骤 5 −现在从"开始"菜单打开 Visual Studio,这将打开以下对话框。首次准备需要一些时间。

第 6 步 − 完成所有这些操作后,您将看到 Visual Studio 的主窗口。

NHibernate 包安装
NHibernate 是 .NET 框架的一个成熟的开源对象关系映射器。它开发活跃,功能齐全,并已在数千个成功的项目中使用。您可以使用以下方法安装 NHibernate 包。
直接下载
从 https://sourceforge.net/ 下载 zip 文件,其中包含所需的所有二进制文件。
提取此 zip 文件并将所有这些二进制文件包含在您的项目中。
使用 NuGet 安装
安装 NHibernate 的另一种方法是使用 NuGet 安装 NHibernate 包,这是迄今为止将 NHibernate 合并到项目中最简单的方法项目。
它将下载所有 NHibernate 依赖项并创建对所有必需程序集的引用。
要安装 NHibernate,请在包管理器控制台中运行以下命令。
install-package NHibernate

您现在可以启动应用程序了。
NHibernate - 入门
在本章中,我们将了解如何使用 NHibernate 启动一个简单的示例。我们将构建一个简单的控制台应用程序。要创建控制台应用程序,我们将使用 Visual Studio 2015,它包含您需要创建的所有功能,并使用 NHibernate 包测试您的应用程序。
以下是使用 Visual Studio 中可用的项目模板创建项目的步骤。
步骤 1 − 打开 Visual Studio 并单击文件 → 新建 → 项目菜单选项。
步骤 2 − 打开一个新的项目对话框。

步骤 3 −从左侧窗格中,选择"模板"→ Visual C# → Windows。
步骤 4 − 在中间窗格中,选择"控制台应用程序"。
步骤 5 − 在"名称"字段中输入项目名称"NHibernateDemoApp",然后单击"确定"继续。
步骤 6 − 一旦 Visual Studio 创建了项目,您将看到解决方案资源管理器窗口中显示许多文件。

如您所知,我们已经创建了一个简单的控制台应用程序项目,现在我们需要将 NHibernate 包包含到我们的控制台项目中。
转到"工具"菜单并选择"NuGet 包管理器"→包管理器控制台,它将打开包管理器控制台窗口。

指定上面包管理器控制台窗口中显示的命令并按回车键,它将下载所有 NHibernate 依赖项并创建对所有所需程序集的引用。安装完成后,您将看到如下图所示的消息。

现在我们已经添加了 NHibernate,我们现在可以开始实施了。因此,我们将从映射一个名为 Student 的非常简单的 表 开始,该表仅具有一个名为 ID 的整数主键以及 FirstName 和 LastName 列。

我们需要一个类来表示这个学生,因此让我们通过右键单击解决方案资源管理器中的项目来创建一个名为 Student 的新类,然后选择添加 → 类,这将打开添加新项目对话框。

在名称字段中输入 Student.cs,单击添加按钮。在这个 Student 类中,我们需要有一个名为 ID 的整数主键,并且我们需要创建这个字符串、FirstName 和 LastName 字段,如下面的 Student 类完整实现所示。
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace NHibernateDemoApp { class Student { public virtual int ID { get; set; } public virtual string LastName { get; set; } public virtual string FirstMidName { get; set; } } }
在 NHibernate 应用程序中处理模型时,最简单的方法是将所有字段都虚拟化。因此,这是我们将使用的简单 NHibernate 模型,并将其映射到后端数据库。
现在让我们转到 Program 类中的 Main 方法并创建一个新的 NHibernate 配置对象。
我们需要提供的第一件事是连接字符串。这是一个特定于数据库的连接字符串,查找连接字符串的最简单方法是右键单击 SQL Server Object Explorer 中的数据库并选择"属性"。

它将打开"属性"窗口,现在向下滚动,您将在"属性"窗口中看到"连接字符串"字段。

复制连接字符串并在代码中指定。以下是 Main 方法的实现,我们需要在其中配置 NHibernate。
using NHibernate.Cfg; using NHibernate.Dialect; using NHibernate.Driver; using System; using System.Linq; using System.Reflection; namespace NHibernateDemoApp { class Program { static void Main(string[] args) { var cfg = new Configuration(); String Data Source = asia13797\sqlexpress; String Initial Catalog = NHibernateDemoDB; String Integrated Security = True; String Connect Timeout = 15; String Encrypt = False; String TrustServerCertificate = False; String ApplicationIntent = ReadWrite; String MultiSubnetFailover = False; cfg.DataBaseIntegration(x = > { x.ConnectionString = "Data Source + Initial Catalog + Integrated Security + Connect Timeout + Encrypt + TrustServerCertificate + ApplicationIntent + MultiSubnetFailover"; x.Driver<SqlClientDriver>(); x.Dialect<MsSql2008Dialect>(); }); cfg.AddAssembly(Assembly.GetExecutingAssembly()); var sefact = cfg.BuildSessionFactory(); using (var session = sefact.OpenSession()) { using (var tx = session.BeginTransaction()) { //perform database logic tx.Commit(); } Console.ReadLine(); } } } }
在连接字符串之后,我们需要提供一个驱动程序,即 SQLClientDriver,然后我们还需要为它提供一种方言,即 SQL Server 的哪个版本,我们将使用 MS SQL 2008。
NHibernate 现在知道如何连接到数据库。我们需要做的另一件事是为它提供一个我们将要映射的模型列表。
我们可以通过添加程序集来做到这一点,因此通过指定 Assembly.GetExecutingAssembly,程序将在这里找到映射文件。映射文件告诉 NHibernate 如何从 C# 类转到数据库表。
SessionFactory 编译初始化 NHibernate 所需的所有元数据。SessionFactory 可用于构建会话,这大致类似于数据库连接。因此,适当的方法是在使用块中使用它。我可以说 var session 等于 sessionFactory.OpenSession,并且我将要在其事务中执行此操作。
一旦打开会话,我们可以告诉会话开始一个新事务,然后我们可以在这里执行一些逻辑。因此,执行一些数据库逻辑并最终提交该事务。
NHibernate - 基本 ORM
在本章中,我们将介绍一些基本映射,您从上一章知道我们有数据库表以及 C# 类定义。我们现在需要一个映射来解释如何从 C# 转换到数据库并返回。
因此,让我们继续添加一个新的 XML 文件,方法是右键单击解决方案资源管理器中的项目并选择添加 →新项目...

在名称字段中输入 Student.hbm.xml。我们需要指定一个默认程序集,即 NHibernateDemoApp,并指定一个默认命名空间。这只会缩短我们将在此文件中进行的许多其他类型定义。
以下是 XML 文件中的实现 −
<?xml version = "1.0" encoding = "utf-8" ?> <hibernate-mapping xmlns = "urn:nhibernate-mapping-2.2" assembly = "NHibernateDemoApp" namespace = "NHibernateDemoApp"> <class name = "Student"> <id name = "ID"> <generator class = "native"/> </id> <property name = "LastName"/> <property name = "FirstMidName"/> </class> </hibernate-mapping>
接下来我们需要定义一个类;这个类将是我们的 Student 类。接下来,我们需要告诉 NHibernate id 的名称,即 ID,我还必须告诉 NHibernate 如何生成 ID,因此我们的生成器将是本机类型。
本机类型生成器意味着在像 SQL Server 这样的数据库中,它将使用标识列,即标识类型。
接下来我们要做的就是给出属性的名称。因此,为 FirstName 和 LastName 添加两个属性。
现在,我们正在从程序集中读取这些映射文件。因此,执行此操作的首选方法是将这些 HBM 文件 嵌入到您的程序集中。我们可以通过简单地设置一个属性来实现这一点。
现在在解决方案资源管理器中右键单击项目并选择属性,您将看到构建操作字段,其中默认选择内容。

从下拉列表中选择嵌入的资源。

因此,这实际上将该 XML 文件嵌入到 NHibernateDemoApp 程序集中。
NHibernate - 基本 CRUD 操作
在本章中,我们将介绍基本 CRUD 操作。现在我们的系统已准备好启动,因为我们已经成功实现了域 Student 类,我们还定义了映射文件并配置了 NHibernate。我们现在可以使用一些查询来执行 CRUD 操作。
创建数据
如您所见,NHibernateDemoDB 数据库中的 Student 表中没有数据。

因此,要添加一些数据,我们需要执行如下所示的 添加/创建 操作。
using (var session = sefact.OpenSession()) { using (var tx = session.BeginTransaction()) { var student1 = new Student { ID = 1, FirstMidName = "Allan", LastName = "Bommer" }; var student2 = new Student { ID = 2, FirstMidName = "Jerry", LastName = "Lewis" }; session.Save(student1); session.Save(student2); tx.Commit(); } Console.ReadLine(); }
如您所见,我们创建了两个学生,然后调用 OpenSession 的 Save() 方法,然后调用 BeginTransaction 的 Commit()。以下是 Program.cs 文件中的完整实现
using NHibernate.Cfg; using NHibernate.Dialect; using NHibernate.Driver; using System; using System.Linq; using System.Reflection; namespace NHibernateDemoApp { class Program { static void Main(string[] args) { var cfg = new Configuration(); String Data Source = asia13797\sqlexpress; String Initial Catalog = NHibernateDemoDB; String Integrated Security = True; String Connect Timeout = 15; String Encrypt = False; String TrustServerCertificate = False; String ApplicationIntent = ReadWrite; String MultiSubnetFailover = False; cfg.DataBaseIntegration(x = > { x.ConnectionString = "Data Source + Initial Catalog + Integrated Security + Connect Timeout + Encrypt + TrustServerCertificate + ApplicationIntent + MultiSubnetFailover"; x.Driver<SqlClientDriver>(); x.Dialect<MsSql2008Dialect>(); }); cfg.AddAssembly(Assembly.GetExecutingAssembly()); var sefact = cfg.BuildSessionFactory(); using (var session = sefact.OpenSession()) { using (var tx = session.BeginTransaction()) { var student1 = new Student { ID = 1, FirstMidName = "Allan", LastName = "Bommer" }; var student2 = new Student { ID = 2, FirstMidName = "Jerry", LastName = "Lewis" }; session.Save(student1); session.Save(student2); tx.Commit(); } Console.ReadLine(); } } } }
现在让我们运行此应用程序,然后转到 SQL Server Object Explorer 并刷新数据库。您将看到上述两名学生现在已添加到 NHibernateDemoDB 数据库中的 Student 表中。

从 Student 表中读取数据
您可以看到现在我们的 student 表中有两条记录。要从表中读取这些记录,我们需要调用 OpenSession 的 CreateCriteria(),如以下代码所示。
using (var session = sefact.OpenSession()) { using (var tx = session.BeginTransaction()) { var students = session.CreateCriteria<Student>().List<Student>(); foreach (var student in students) { Console.WriteLine("{0} {1} {2}", student.ID,student.FirstMidName, student.LastName); } tx.Commit(); } Console.ReadLine(); }
因此,如果您想要记录列表,那么我们只需简单地说学生类型的列表即可。
现在使用 foreach 遍历所有学生,并在控制台上打印 ID、FirstMidName 和 LastName。现在,让我们再次运行此应用程序,您将在控制台窗口中看到以下输出。
1 Allan Bommer 2 Jerry Lewis
您还可以使用以下代码在 OpenSession 的 Get() 方法中指定 ID 来检索任何记录。
using (var session = sefact.OpenSession()) { using (var tx = session.BeginTransaction()) { var students = session.CreateCriteria<Student>().List<Student>(); foreach (var student in students) { Console.WriteLine("{0} {1} {2}", student.ID, student.FirstMidName, student.LastName); } var stdnt = session.Get<Student>(1); Console.WriteLine("Retrieved by ID"); Console.WriteLine("{0} {1} {2}", stdnt.ID, stdnt.FirstMidName, stdnt.LastName); tx.Commit(); } Console.ReadLine(); }
现在,当您运行应用程序时,您将看到以下输出。
1 Allan Bommer 2 Jerry Lewis Retrieved by ID 1 Allan Bommer
更新记录
要更新表中的记录,我们需要先获取该特定记录,然后通过调用 OpenSession 的 Update() 方法来更新该记录,如以下代码所示。
using (var session = sefact.OpenSession()) { using (var tx = session.BeginTransaction()) { var students = session.CreateCriteria<Student>().List<Student>(); foreach (var student in students) { Console.WriteLine("{0} {1} {2}", student.ID, student.FirstMidName, student.LastName); } var stdnt = session.Get<Student>(1); Console.WriteLine("Retrieved by ID"); Console.WriteLine("{0} {1} {2}", stdnt.ID, stdnt.FirstMidName, stdnt.LastName); Console.WriteLine("Update the last name of ID = {0}", stdnt.ID); stdnt.LastName = "Donald"; session.Update(stdnt); Console.WriteLine(" Fetch the complete list again "); foreach (var student in students) { Console.WriteLine("{0} {1} {2}", student.ID, student.FirstMidName, student.LastName); } tx.Commit(); } Console.ReadLine(); }
现在,当您运行应用程序时,您将看到以下输出。
1 Allan Bommer 2 Jerry Lewis Retrieved by ID 1 Allan Bommer Update the last name of ID = 1 Fetch the complete list again 1 Allan Donald 2 Jerry Lewis
如您所见,ID 等于 1 的 LastName 从 Bommer 更新为 Donald。
删除记录
要从表中删除任何记录,我们需要先获取该特定记录,然后通过调用 OpenSession 的 Delete() 方法删除该记录,如以下代码所示。
using (var session = sefact.OpenSession()) { using (var tx = session.BeginTransaction()) { var students = session.CreateCriteria<Student>().List<Student>(); foreach (var student in students) { Console.WriteLine("{0} {1} {2}", student.ID, student.FirstMidName, student.LastName); } var stdnt = session.Get<Student>(1); Console.WriteLine("Retrieved by ID"); Console.WriteLine("{0} {1} {2}", stdnt.ID, stdnt.FirstMidName, stdnt.LastName); Console.WriteLine("Delete the record which has ID = {0}", stdnt.ID); session.Delete(stdnt); Console.WriteLine(" Fetch the complete list again "); foreach (var student in students) { Console.WriteLine("{0} {1} {2}", student.ID, student.FirstMidName, student.LastName); } tx.Commit(); } Console.ReadLine(); }
现在,当您运行应用程序时,您将看到以下输出。
1 Allan Donald 2 Jerry Lewis Retrieved by ID 1 Allan Bommer Delete the record which has ID = 1 Fetch the complete list again 2 Jerry Lewis
如您所见,数据库中 ID 等于 1 的记录不再可用。您还可以在 SQL Server 对象资源管理器中查看数据库。

NHibernate - Profiler
在本章中,我们将了解如何检索、更新、创建和删除数据库中的所有记录,以及这些查询究竟是如何执行的?
要理解所有这些,我们可以简单地在配置中添加一个选项,将 SQL 记录在控制台中。以下是将记录 SQL 查询 − 的简单语句
x.LogSqlInConsole = true;
现在,NHibernateDemoDB 数据库中的学生表中有两条记录。让我们从数据库中检索所有记录,如以下代码所示。
using NHibernate.Cfg; using NHibernate.Dialect; using NHibernate.Driver; using System; using System.Linq; using System.Reflection; namespace NHibernateDemoApp { class Program { static void Main(string[] args) { var cfg = new Configuration(); String Data Source = asia13797\sqlexpress; String Initial Catalog = NHibernateDemoDB; String Integrated Security = True; String Connect Timeout = 15; String Encrypt = False; String TrustServerCertificate = False; String ApplicationIntent = ReadWrite; String MultiSubnetFailover = False; cfg.DataBaseIntegration(x = > { x.ConnectionString = "Data Source + Initial Catalog + Integrated Security + Connect Timeout + Encrypt + TrustServerCertificate + ApplicationIntent + MultiSubnetFailover"; x.Driver<SqlClientDriver>(); x.Dialect<MsSql2008Dialect>(); x.LogSqlInConsole = true; }); cfg.AddAssembly(Assembly.GetExecutingAssembly()); var sefact = cfg.BuildSessionFactory(); using (var session = sefact.OpenSession()) { using (var tx = session.BeginTransaction()) { Console.WriteLine(" Fetch the complete list again "); var students = session.CreateCriteria<Student>().List<Student>(); foreach (var student in students) { Console.WriteLine("{0} {1} {2}", student.ID, student.FirstMidName, student.LastName); } tx.Commit(); } Console.ReadLine(); } } } }
让我们继续再次运行此应用程序,您将看到以下输出 −
NHibernate: SELECT this_.ID as ID0_0_, this_.LastName as LastName0_0_, this_.FirstMidName as FirstMid3_0_0_ FROM Student this_ Fetch the complete list again 3 Allan Bommer 4 Jerry Lewis
如您所见,select 子句被发送到数据库,它实际上类似于将检索 ID、FirstMidName 和 LastName 的子句。因此,所有这些都被发送到数据库并在那里进行处理,而不是将大量记录带回您的服务器并在服务器端进行处理。
NHibernate Profiler
查看这些结果的另一种方法是使用 NHibernate Profiler。NHibernate Profiler 是一种商业工具,但它对于使用 NHibernate 应用程序非常有用。您可以从 NuGet 轻松将 NHibernate Profiler 安装到您的应用程序中。
让我们从工具菜单中选择 NuGet 包管理器 → 包管理器控制台转到 NuGet 管理器控制台。它将打开包管理器控制台窗口。输入以下命令并按 Enter。
PM> install-package NHibernateProfiler
它将安装 NHibernate Profiler 所需的所有二进制文件,成功安装后,您将看到以下消息。

您还将看到,一旦安装完成,NHibernate Profiler 就会启动。它需要许可证才能使用,但出于演示目的,我们可以使用 NHibernate Profiler 的 30 天试用版。
现在,NHibernate Profiler 已针对 Web 应用程序进行了优化,您将看到它在解决方案资源管理器中添加了 App_Start 文件夹。为了使所有这些保持简单,删除 App_Start 文件夹,您还会发现在 Program 类的 Main 方法开头添加了一个语句。
App_Start.NHibernateProfilerBootstrapper.PreStart();
也删除此语句,只需添加一个简单的调用 NHibernateProfiler.Initialize,如以下代码所示。
using HibernatingRhinos.Profiler.Appender.NHibernate; using NHibernate.Cfg; using NHibernate.Dialect; using NHibernate.Driver; using System; using System.Linq; using System.Reflection; namespace NHibernateDemoApp { class Program { static void Main(string[] args) { NHibernateProfiler.Initialize(); var cfg = new Configuration(); String Data Source = asia13797\sqlexpress; String Initial Catalog = NHibernateDemoDB; String Integrated Security = True; String Connect Timeout = 15; String Encrypt = False; String TrustServerCertificate = False; String ApplicationIntent = ReadWrite; String MultiSubnetFailover = False; cfg.DataBaseIntegration(x = > { x.ConnectionString = "Data Source + Initial Catalog + Integrated Security + Connect Timeout + Encrypt + TrustServerCertificate + ApplicationIntent + MultiSubnetFailover"; x.Driver<SqlClientDriver>(); x.Dialect<MsSql2008Dialect>(); x.LogSqlInConsole = true; }); cfg.AddAssembly(Assembly.GetExecutingAssembly()); var sefact = cfg.BuildSessionFactory(); using (var session = sefact.OpenSession()) { using (var tx = session.BeginTransaction()){ var students = session.CreateCriteria<Student>().List<Student>(); Console.WriteLine(" Fetch the complete list again "); foreach (var student in students) { Console.WriteLine("{0} {1} {2}", student.ID, student.FirstMidName, student.LastName); } tx.Commit(); } Console.ReadLine(); } } } }
现在,当您运行应用程序时,它将把数据发送到 NHibernate Profiler 应用程序。

您可以在这里看到,我们有一个很好的显示,显示我们已经启动了事务,SQL 正在以一种很好的格式对数据库执行什么操作。
因此,这对于确定 NHibernate 应用程序内部究竟发生了什么非常有用。一旦应用程序达到一定的复杂程度,它就会变得非常有用,这时您需要更像 SQL Profiler 的东西,但需要了解 NHibernate。
将 Intelliesnse 添加到映射文件
在本章中,我们将向我们的 NHibernate 映射文件 (*.hbm.xml 文件) 添加 IntelliSense。正如您在映射域 Student 类时所观察到的,目前我们没有可用的 IntelliSense。拥有 XML 架构 非常有用。因此,在本章中,您将了解如何在 Visual Studio 中为这些 NHibernate XML 文件添加 IntelliSense。
打开映射文件,您将看到 XML 菜单选项出现在主菜单中。

选择 XML → Schemas… 菜单选项,它将显示 XML Schemas 对话框。

选择对话框右上角的 Add… 按钮,打开文件对话框。现在转到 packages 文件夹,它位于项目的解决方案文件夹中,您将看到项目中包含的不同包。

现在,双击 NHibernate.4.*** 文件夹,您将看到定义 NHibernate 配置和映射的两个架构 (*.xsd) 文件或 XML 架构定义文件。

选择这两个架构文件并单击打开按钮。

您可以看到 NHibernate 架构已添加到 XML 中Schemas 对话框。单击 OK 按钮。现在,让我们开始一个新的属性标签,您将看到我们在这里拥有完整的 IntelliSense。

IntelliSense 现在可供您使用,这在对象关系映射期间节省了大量时间。
NHibernate - 数据类型映射
在本章中,我们将介绍映射数据类型。映射实体很简单,实体类始终使用 <class>、<subclass> 和 <joined-subclass> 映射元素映射到数据库表。值类型需要更多的东西,这就是需要映射类型的地方。
NHibernate 能够映射各种数据类型。以下是支持的最常见数据类型的列表。
映射类型 | .NET 类型 | System.Data.DbType |
---|---|---|
Int16 | System.Int16 | DbType.Int16 |
Int32 | System.Int32 | DbType.Int32 |
Int64 | System.Int64 | DbType.Int64 |
Single | System.Single | DbType.Single |
Double | System.Double | DbType.Double |
Decimal | System.Decimal | DbType.Decimal |
String | System.String | DbType.String |
AnsiString | System.String | DbType.AnsiString |
Byte | System.Byte | DbType.Byte |
Char | System.Char | DbType.StringFixedLength—one character |
AnsiChar | System.Char | DbType.AnsiStringFixedLength—one character |
Boolean | System.Boolean | DbType.Boolean |
Guid | System.Guid | DbType.Guid |
PersistentEnum | System.Enum(an enumeration) | DbType for the underlying value |
TrueFalse | System.Boolean | DbType.AnsiStringFixedLength—either 'T' or 'F' |
YesNo | System.Boolean | DbType.AnsiStringFixedLength—either 'Y' or 'N' |
DateTime | DateTime | DbType.DateTime—ignores milliseconds |
Ticks | System.DateTime | DbType.Int64 |
TimeSpan | System.TimeSpan | DbType.Int64 |
Timestamp | System.DateTime | DbType.DateTime—as specific as the database supports |
Binary | System.Byte[] | DbType.Binary |
BinaryBlob | System.Byte[] | DbType.Binary |
StringClob | System.String | DbType.String |
Serializable | Any System.Object marked with SerializableAttribute | DbType.Binary |
CultureInfo | System.Globalization.CultureInfo | DbType.String—five characters for culture |
Type | System.Type | DbType.String holding the Assembly Qualified Name |
上表详细解释了下面提到的指针。
从简单的数字类型到字符串,可以使用varchars、chars等以多种方式映射,以及字符串 blob 和数据库支持的各种类型。
它还能够映射布尔值,既可以映射到使用零和一的字段,也可以映射到包含 true、false 或 T 和 F 的字符字段。
有多种方法可以定义如何映射到后端,数据库中的布尔值。
我们可以处理DateTime的映射,包括和排除时区偏移、夏令时等。
我们还可以映射枚举;我们可以将它们映射到字符串或它们的底层数值。
让我们看一个简单的例子,其中我们在数据库和 Student 类中都有相同的属性名称。
现在让我们将 Student 类中的 FirstMidName 更改为 FirstName,我们不会更改 FirstMidName 列,但我们将了解如何告诉 NHibernate 执行此转换。以下是更新后的学生班。
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace NHibernateDemoApp { class Student { public virtual int ID { get; set; } public virtual string LastName { get; set; } public virtual string FirstName { get; set; } } }
下面是 NHibernate 映射文件的实现。
<?xml version = "1.0" encoding = "utf-8" ?> <hibernate-mapping xmlns = "urn:nhibernate-mapping-2.2" assembly = "NHibernateDemoApp" namespace = "NHibernateDemoApp"> <class name = "Student"> <id name = "ID"> <generator class = "native"/> </id> <property name = "LastName"/> <property name = "FirstName" column = "FirstMidName" type = "String"/> </class> </hibernate-mapping>
在此示例中,假设 FirstName 字段是 .NET 字符串,FirstMidName 列是 SQL nvarchar。现在要告诉 NHibernate 如何执行此转换,请将名称设置为 FirstName,将列设置为 FirstMidName,并将映射类型指定为 String,这适用于此特定转换。
以下是 Program.cs 文件实现。
using HibernatingRhinos.Profiler.Appender.NHibernate; using NHibernate.Cfg; using NHibernate.Dialect; using NHibernate.Driver; using System; using System.Linq; using System.Reflection; namespace NHibernateDemoApp { class Program { static void Main(string[] args) { NHibernateProfiler.Initialize(); var cfg = new Configuration(); String Data Source = asia13797\sqlexpress; String Initial Catalog = NHibernateDemoDB; String Integrated Security = True; String Connect Timeout = 15; String Encrypt = False; String TrustServerCertificate = False; String ApplicationIntent = ReadWrite; String MultiSubnetFailover = False; cfg.DataBaseIntegration(x = > { x.ConnectionString = "Data Source + Initial Catalog + Integrated Security + Connect Timeout + Encrypt + TrustServerCertificate + ApplicationIntent + MultiSubnetFailover"; x.Driver<SqlClientDriver>(); x.Dialect<MsSql2008Dialect>(); x.LogSqlInConsole = true; }); cfg.AddAssembly(Assembly.GetExecutingAssembly()); var sefact = cfg.BuildSessionFactory(); using (var session = sefact.OpenSession()) { using (var tx = session.BeginTransaction()) { var students = session.CreateCriteria<Student>().List<Student>(); Console.WriteLine(" Fetch the complete list again "); foreach (var student in students) { Console.WriteLine("{0} {1} {2}", student.ID, student.FirstName, student.LastName); } tx.Commit(); } Console.ReadLine(); } } } }
现在,当您运行应用程序时,您将看到以下输出。
NHibernate: SELECT this_.ID as ID0_0_, this_.LastName as LastName0_0_, this_.FirstMidName as FirstMid3_0_0_ FROM Student this_ Fetch the complete list again 3 Allan Bommer 4 Jerry Lewis
如您所见,它已将不同的属性名称映射到数据库中的列名称。
让我们看另一个示例,其中我们将在 enum 类型的 Student 类中添加另一个属性。以下是 Student 类实现。
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace NHibernateDemoApp { class Student { public virtual int ID { get; set; } public virtual string LastName { get; set; } public virtual string FirstName { get; set; } public virtual StudentAcademicStanding AcademicStanding { get; set; } } public enum StudentAcademicStanding { Excellent, Good, Fair, Poor, Terrible } }
如您所见,枚举可能具有各种不同的值,例如,优秀、良好、一般、较差和糟糕。
跳转到映射文件,您可以看到映射文件中列出了这些属性中的每一个,包括新添加的 AcademicStanding 属性。
<?xml version = "1.0" encoding = "utf-8" ?> <hibernate-mapping xmlns = "urn:nhibernate-mapping-2.2" assembly = "NHibernateDemoApp" namespace = "NHibernateDemoApp"> <class name = "Student"> <id name = "ID"> <generator class = "native"/> </id> <property name = "LastName"/> <property name = "FirstName" column = "FirstMidName" type = "String"/> <property name = "AcademicStanding"/> </class> </hibernate-mapping>
现在我们还需要更改数据库,因此请转到 SQL Server 对象资源管理器并右键单击数据库并选择新建查询…选项。

它将打开查询编辑器,然后指定以下查询。
DROP TABLE [dbo].[Student] CREATE TABLE [dbo].[Student] ( [ID] INT IDENTITY (1, 1) NOT NULL, [LastName] NVARCHAR (MAX) NULL, [FirstMidName] NVARCHAR (MAX) NULL, [AcademicStanding] NCHAR(10) NULL, CONSTRAINT [PK_dbo.Student] PRIMARY KEY CLUSTERED ([ID] ASC) );
此查询将首先删除现有的学生表,然后创建一个新表。

单击"执行"图标,如上所示。查询成功执行后,您将看到一条消息。
展开数据库和表下拉菜单,然后右键单击 Student 表并选择"视图设计器"。

现在,您将看到新创建的表,该表还具有新属性 AcademicStanding。

让我们添加两条记录,如以下 Program.cs 文件所示。
using HibernatingRhinos.Profiler.Appender.NHibernate; using NHibernate.Cfg; using NHibernate.Dialect; using NHibernate.Driver; using System; using System.Linq; using System.Reflection; namespace NHibernateDemoApp { class Program { static void Main(string[] args) { NHibernateProfiler.Initialize(); var cfg = new Configuration(); String Data Source = asia13797\sqlexpress; String Initial Catalog = NHibernateDemoDB; String Integrated Security = True; String Connect Timeout = 15; String Encrypt = False; String TrustServerCertificate = False; String ApplicationIntent = ReadWrite; String MultiSubnetFailover = False; cfg.DataBaseIntegration(x = > { x.ConnectionString = "Data Source + Initial Catalog + Integrated Security + Connect Timeout + Encrypt + TrustServerCertificate + ApplicationIntent + MultiSubnetFailover"; x.Driver<SqlClientDriver>(); x.Dialect<MsSql2008Dialect>(); }); cfg.AddAssembly(Assembly.GetExecutingAssembly()); var sefact = cfg.BuildSessionFactory(); using (var session = sefact.OpenSession()) { using (var tx = session.BeginTransaction()) { var student1 = new Student { ID = 1, FirstName = "Allan", LastName = "Bommer", AcademicStanding = StudentAcademicStanding.Excellent }; var student2 = new Student { ID = 2, FirstName = "Jerry", LastName = "Lewis", AcademicStanding = StudentAcademicStanding.Good }; session.Save(student1); session.Save(student2); var students = session.CreateCriteria<Student>().List<Student>(); Console.WriteLine(" Fetch the complete list again "); foreach (var student in students) { Console.WriteLine("{0} {1} {2} {3}", student.ID, student.FirstName, student.LastName, student.AcademicStanding); } tx.Commit(); } Console.ReadLine(); } } } }
现在让我们运行您的应用程序,您将在控制台窗口上看到以下输出。
Fetch the complete list again 1 Allan Bommer Excellent 2 Jerry Lewis Good
现在让我们通过右键单击 Student 表来查看数据库。

选择"查看数据",您将看到 Student 表中的两条记录,如以下屏幕截图所示。

您可以看到添加了两条记录,Allan 的 AcademicStanding 为 0,Jerry 的 AcademicStanding 为 1。这是因为在 .Net 中,第一个枚举值默认为 0,如果您查看 StudentAcademicStanding,则为 Excellent。而在 Student.cs 文件中,Good 是第二个,因此它的值为 1。
NHibernate - 配置
在本章中,我们将介绍 NHibernate 配置。我们可以通过不同的方式配置 NHibernate。它分为两大类
- 基于 XML 的配置
- 基于代码的配置
基于代码的配置
基于代码的配置内置于 NHibernate 中。它是在 NHibernate 3 左右引入的,到目前为止我们一直在使用代码库配置。
String Data Source = asia13797\sqlexpress; String Initial Catalog = NHibernateDemoDB; String Integrated Security = True; String Connect Timeout = 15; String Encrypt = False; String TrustServerCertificate = False; String ApplicationIntent = ReadWrite; String MultiSubnetFailover = False; cfg.DataBaseIntegration(x = > { x.ConnectionString = "Data Source + Initial Catalog + Integrated Security + Connect Timeout + Encrypt + TrustServerCertificate + ApplicationIntent + MultiSubnetFailover"; x.Driver<SqlClientDriver>(); x.Dialect<MsSql2008Dialect>(); x.LogSqlInConsole = true; }); cfg.AddAssembly(Assembly.GetExecutingAssembly());
所有配置均在 C# 代码中指定。您可以在此处看到,我们已获得新的配置对象,然后我们使用 NHibernate 3.1 中引入的 loquacious 配置 来配置数据库。我们使用的连接字符串、连接的数据库以及要使用的方言。我们还将映射程序集直接添加到此处。
基于 XML 的配置
如果您使用基于 XML 的配置,则可以使用 hibernate.cfg.xml 文件,它只是一个使用 NHibernate 模式的独立 xml 文件,或者您可以将该 NHibernate 特定配置嵌入到您的应用程序或 web.cfg 中。 hibernate.cfg.xml 是默认名称,但我们也可以为该 xml 文件使用任意名称。
让我们通过向 NHibernateDemoApp 项目添加一个新的 xml 文件并将其命名为 hibernate.cfg.xml 来查看基于 XML 的配置。
在 hibernate.cfg.xml 文件中输入以下信息。
<?xml version = "1.0" encoding = "utf-8" ?> <hibernate-configuration xmlns = "urn:nhibernate-configuration-2.2"> <session-factory> <property name = "connection.connection_string"> Data Source = asia13797\sqlexpress; Initial Catalog = NHibernateDemoDB; Integrated Security = True; Connect Timeout = 15; Encrypt = False; TrustServerCertificate = False; ApplicationIntent = ReadWrite; MultiSubnetFailover = False; </property> <property name = "connection.driver_class"> NHibernate.Driver.SqlClientDriver </property> <property name = "dialect"> NHibernate.Dialect.MsSql2008Dialect </property> <mapping assembly = "NHibernateDemoApp"/> </session-factory> </hibernate-configuration>
如您在上面的 xml 文件中看到的,我们指定了与 C# 中提到的相同的配置。
现在让我们从 Program.cs 文件中注释此配置,然后调用 Configure() 方法,该方法将加载 hibernate.cfg.xml 文件,如下所示。
using HibernatingRhinos.Profiler.Appender.NHibernate; using NHibernate.Cfg; using NHibernate.Dialect; using NHibernate.Driver; using System; using System.Linq; using System.Reflection; namespace NHibernateDemoApp { class Program { static void Main(string[] args) { NHibernateProfiler.Initialize(); var cfg = new Configuration(); //cfg.DataBaseIntegration(x => //{ // x.ConnectionString = "Data Source = asia13797;\sqlexpress Initial Catalog = NHibernateDemoDB; Integrated Security = True; Connect Timeout = 15; Encrypt =False; TrustServerCertificate = False; ApplicationIntent = ReadWrite; MultiSubnetFailover = False"; // x.Driver<SqlClientDriver>(); // x.Dialect<MsSql2008Dialect>(); // x.LogSqlInConsole = true; //}); //cfg.AddAssembly(Assembly.GetExecutingAssembly()); cfg.Configure(); var sefact = cfg.BuildSessionFactory(); using (var session = sefact.OpenSession()) { using (var tx = session.BeginTransaction()) { var students = session.CreateCriteria<Student>().List<Student>(); Console.WriteLine(" Fetch the complete list again "); foreach (var student in students) { Console.WriteLine("{0} {1} {2} {3}", student.ID, student.FirstName, student.LastName, student.AcademicStanding); } tx.Commit(); } Console.ReadLine(); } } } }
让我们再次运行您的应用程序,您将看到相同的输出。
Fetch the complete list again 1 Allan Bommer Excellent 2 Jerry Lewis Good
NHibernate - 覆盖配置
在本章中,我们将介绍如何覆盖 NHibernate 配置。您只需记住几件事。
首先,NHibernate 中的配置是附加的。
因此,您不必只使用单个 xml 文件,也不必只使用基于代码的配置或流畅的 NHibernate。
您可以根据要如何配置应用程序来混合和搭配所有这些方法。
需要记住的重要一点是,最后配置获胜。
在下面的示例中,您可以看到我们创建了配置对象,使用基于代码的配置对其进行配置,最后调用 cfg.configure() 方法,该方法加载 hibernate.cfg.xml 文件。
String Data Source = asia13797\sqlexpress; String Initial Catalog = NHibernateDemoDB; String Integrated Security = True; String Connect Timeout = 15; String Encrypt = False; String TrustServerCertificate = False; String ApplicationIntent = ReadWrite; String MultiSubnetFailover = False; cfg.DataBaseIntegration(x = > { x.ConnectionString = "Data Source + Initial Catalog + Integrated Security + Connect Timeout + Encrypt + TrustServerCertificate + ApplicationIntent + MultiSubnetFailover"; x.Driver<SqlClientDriver>(); x.Dialect<MsSql2008Dialect>(); x.LogSqlInConsole = true; }); cfg.Configure();
因此,hibernate.cfg.xml 中的任何内容都会覆盖基于代码的配置所设置的设置。
通过反转这两个过程,我们可以在 hibernate.cfg.xml 中获得默认值,然后在基于代码的配置中进行覆盖。
如果您使用基于代码的配置,则没有什么可以排除的,也没有什么可以阻止您使用 hibernate.cfg.xml 文件。
让我们看一个简单的示例,在该示例中,我们将使用基于 xml 和基于代码的配置的混合来覆盖配置。
我们还使用以下代码将连接字符串移动到 app.config 文件。
<?xml version = "1.0" encoding = "utf-8" ?> <configuration> <startup> <supportedRuntime version = "v4.0" sku = ".NETFramework,Version = v4.5" /> </startup> <connectionStrings> <add name = "default" connectionString = "Data Source = asia13797\sqlexpress; Initial Catalog = NHibernateDemoDB; Integrated Security = True; Connect Timeout = 15; Encrypt = False; TrustServerCertificate = False; ApplicationIntent = ReadWrite; MultiSubnetFailover = False"/> </connectionStrings> </configuration>
连接字符串位于某个具有默认名称的 app.config 文件中。现在,我们需要在 hibernate.cfg.xml 文件中提及默认名称,而不是连接字符串。
<?xml version = "1.0" encoding = "utf-8" ?> <hibernate-configuration xmlns = "urn:nhibernate-configuration-2.2"> <session-factory> <property name = "connection.connection_string">default</property> <property name = "connection.driver_class"> NHibernate.Driver.SqlClientDriver </property> <property name = "dialect"> NHibernate.Dialect.MsSql2008Dialect </property> <mapping assembly = "NHibernateDemoApp"/> </session-factory> </hibernate-configuration>
让我们从基于代码的配置中注释掉连接字符串部分、驱动程序和方言部分,因为程序将从 hibernate.cfg.xml 文件中读取它,而 LogSqlInConsole 部分将保留在基于代码的配置中。
using HibernatingRhinos.Profiler.Appender.NHibernate; using NHibernate.Cfg; using NHibernate.Dialect; using NHibernate.Driver; using System; using System.Linq; using System.Reflection; namespace NHibernateDemoApp { class Program { static void Main(string[] args) { NHibernateProfiler.Initialize(); var cfg = new Configuration(); String Data Source = asia13797\sqlexpress; String Initial Catalog = NHibernateDemoDB; String Integrated Security = True; String Connect Timeout = 15; String Encrypt = False; String TrustServerCertificate = False; String ApplicationIntent = ReadWrite; String MultiSubnetFailover = False; cfg.DataBaseIntegration(x = > { //x.ConnectionString = "Data Source + Initial Catalog + Integrated Security + Connect Timeout + Encrypt + TrustServerCertificate + ApplicationIntent + MultiSubnetFailover"; //x.Driver<SqlClientDriver>(); //x.Dialect<MsSql2008Dialect>(); x.LogSqlInConsole = true; }); cfg.Configure(); cfg.AddAssembly(Assembly.GetExecutingAssembly()); var sefact = cfg.BuildSessionFactory(); using (var session = sefact.OpenSession()) { using (var tx = session.BeginTransaction()) { var students = session.CreateCriteria<Student>().List<Student>(); Console.WriteLine(" Fetch the complete list again "); foreach (var student in students) { Console.WriteLine("{0} {1} {2} {3}", student.ID, student.FirstName, student.LastName, student.AcademicStanding); } tx.Commit(); } Console.ReadLine(); } } } }
现在当您运行应用程序时,您将看到程序已经从基于代码的配置和 hibernate.cfg.xml 文件中的其他配置中读取了日志。
NHibernate: SELECT this_.ID as ID0_0_, this_.LastName as LastName0_0_, this_.FirstMidName as FirstMid3_0_0_, this_.AcademicStanding as Academic4_0_0_ FROM Student this_ Fetch the complete list again 1 Allan Bommer Excellent 2 Jerry Lewis Good
现在我们在 hibernate.cfg.xml 文件中获得了部分配置,其中一部分配置位于基于代码的配置中,并且根据基于代码的配置与 configure() 的调用顺序,我们可以改变其中哪个配置覆盖另一个配置。
NHibernate - 批量大小
在本章中,我们将介绍批量大小更新。批处理大小允许您控制在支持的数据库中单次往返的更新次数。
从 NHibernate 3.2 开始,更新批处理大小已默认。
但是,如果您使用的是早期版本或需要调整 NHibernate 应用程序,则应查看更新批处理大小,这是一个非常有用的参数,可用于调整 NHibernate 的性能。
实际上,批处理大小控制将多少条插入推送到数据库中。
目前,只有 SQL Server 和 Oracle 支持此选项,因为底层数据库提供程序需要支持查询批处理。
让我们看一个简单的例子,其中我们将批处理大小设置为 10,这将在数据库中插入 10 条记录设置。
cfg.DataBaseIntegration(x => { x.ConnectionString = "default"; x.Driver<SqlClientDriver>(); x.Dialect<MsSql2008Dialect>(); x.LogSqlInConsole = true; x.BatchSize = 10; });
以下是完整的实现,其中将向数据库添加 25 条记录。
using HibernatingRhinos.Profiler.Appender.NHibernate; using NHibernate.Cfg; using NHibernate.Dialect; using NHibernate.Driver; using System; using System.Linq; using System.Reflection; namespace NHibernateDemoApp { class Program { static void Main(string[] args) { NHibernateProfiler.Initialize(); var cfg = new Configuration(); String Data Source = asia13797\sqlexpress; String Initial Catalog = NHibernateDemoDB; String Integrated Security = True; String Connect Timeout = 15; String Encrypt = False; String TrustServerCertificate = False; String ApplicationIntent = ReadWrite; String MultiSubnetFailover = False; cfg.DataBaseIntegration(x = > { x.ConnectionString = "Data Source + Initial Catalog + Integrated Security + Connect Timeout + Encrypt + TrustServerCertificate + ApplicationIntent + MultiSubnetFailover"; x.Driver>SqlClientDriver<(); x.Dialect>MsSql2008Dialect>(); x.LogSqlInConsole = true; x.BatchSize = 10; }); //cfg.Configure(); cfg.AddAssembly(Assembly.GetExecutingAssembly()); var sefact = cfg.BuildSessionFactory(); using (var session = sefact.OpenSession()) { using (var tx = session.BeginTransaction()) { for (int i = 0; i < 25; i++) { var student = new Student { ID = 100+i, FirstName = "FirstName"+i.ToString(), LastName = "LastName" + i.ToString(), AcademicStanding = StudentAcademicStanding.Good }; session.Save(student); } tx.Commit(); var students = session.CreateCriteria<Student>().List<Student>(); Console.WriteLine(" Fetch the complete list again "); foreach (var student in students) { Console.WriteLine("{0} {1} {2} {3}", student.ID,student.FirstName, student.LastName, student.AcademicStanding); } } Console.ReadLine(); } } } }
现在让我们运行您的应用程序,您会看到所有这些更新都跳转到 NHibernate 分析器。我们有 26 次单独的数据库往返,其中 25 次用于插入,一次用于检索学生列表。
现在,为什么会这样?原因是 NHibernate 需要执行 select scope Identity,因为我们在映射文件中使用本机标识符生成策略来获取 ID,如以下代码所示。
<?xml version = "1.0" encoding = "utf-8" ?> <hibernate-mapping xmlns = "urn:nhibernate-mapping-2.2" assembly = "NHibernateDemoApp" namespace = "NHibernateDemoApp"> <class name = "Student"> <id name = "ID"> <generator class = "native"/> </id> <property name = "LastName"/> <property name = "FirstName" column = "FirstMidName" type = "String"/> <property name = "AcademicStanding"/> </class> </hibernate-mapping>
因此,我们需要使用不同的方法,例如 guid.comb 方法。如果我们要转到 guid.comb,我们需要转到我们的客户并将其更改为 guid。这样就可以正常工作了。现在,让我们使用以下代码将本机更改为 guid.comb。
<?xml version = "1.0" encoding = "utf-8" ?> <hibernate-mapping xmlns = "urn:nhibernate-mapping-2.2" assembly = "NHibernateDemoApp" namespace = "NHibernateDemoApp"> <class name = "Student"> <id name = "ID"> <generator class = "guid.comb"/> </id> <property name = "LastName"/> <property name = "FirstName" column = "FirstMidName" type = "String"/> <property name = "AcademicStanding"/> </class> </hibernate-mapping>
因此,数据库负责生成这些 ID。NHibernate 找出生成的 ID 的唯一方法是事后立即选择它。否则,如果我们创建了一批学生,它将无法匹配所创建学生的 ID。
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace NHibernateDemoApp { class Student { public virtual Guid ID { get; set; } public virtual string LastName { get; set; } public virtual string FirstName { get; set; } public virtual StudentAcademicStanding AcademicStanding { get; set; } } public enum StudentAcademicStanding { Excellent, Good, Fair, Poor, Terrible } }
我们只需要更新数据库。让我们删除学生表并通过指定以下查询创建一个新表,因此转到 SQL Server 对象资源管理器并右键单击数据库并选择新查询…选项。
它将打开查询编辑器,然后指定以下查询。
DROP TABLE [dbo].[Student] CREATE TABLE [dbo].[Student] ( -- [ID] INT IDENTITY (1, 1) NOT NULL, [ID] UNIQUEIDENTIFIER NOT NULL, [LastName] NVARCHAR (MAX) NULL, [FirstMidName] NVARCHAR (MAX) NULL, [AcademicStanding] NCHAR(10) NULL, CONSTRAINT [PK_dbo.Student] PRIMARY KEY CLUSTERED ([ID] ASC) );
此查询将首先删除现有的学生表,然后创建一个新表。如您所见,我们使用了 UNIQUEIDENTIFIER 而不是使用整数主键作为 ID。
执行此查询,然后转到 Designer 视图,您将看到现在使用唯一标识符创建了 ID,如下图所示。

现在我们需要在插入数据时从 program.cs 文件中删除 ID,因为现在它将自动为其生成 guids。
using HibernatingRhinos.Profiler.Appender.NHibernate; using NHibernate.Cfg; using NHibernate.Dialect; using NHibernate.Driver; using System; using System.Linq; using System.Reflection; namespace NHibernateDemoApp { class Program { static void Main(string[] args) { NHibernateProfiler.Initialize(); var cfg = new Configuration(); String Data Source = asia13797\sqlexpress; String Initial Catalog = NHibernateDemoDB; String Integrated Security = True; String Connect Timeout = 15; String Encrypt = False; String TrustServerCertificate = False; String ApplicationIntent = ReadWrite; String MultiSubnetFailover = False; cfg.DataBaseIntegration(x = > { x.ConnectionString = "Data Source + Initial Catalog + Integrated Security + Connect Timeout + Encrypt + TrustServerCertificate + ApplicationIntent + MultiSubnetFailover"; x.Driver<SqlClientDriver>(); x.Dialect<MsSql2008Dialect>(); x.LogSqlInConsole = true; x.BatchSize = 10; }); //cfg.Configure(); cfg.AddAssembly(Assembly.GetExecutingAssembly()); var sefact = cfg.BuildSessionFactory(); using (var session = sefact.OpenSession()) { using (var tx = session.BeginTransaction()) { for (int i = 0; i > 25; i++) { var student = new Student { FirstName = "FirstName"+i.ToString(), LastName = "LastName" + i.ToString(), AcademicStanding = StudentAcademicStanding.Good }; session.Save(student); } tx.Commit(); var students = session.CreateCriteria<Student>().List<Student>(); Console.WriteLine(" Fetch the complete list again "); foreach (var student in students) { Console.WriteLine("{0} {1} {2} {3}", student.ID, student.FirstName,student.LastName, student.AcademicStanding); } } Console.ReadLine(); } } } }
现在再次运行应用程序并查看 NHibernate 分析器。现在 NHibernate 分析器将只进行 4 次往返,而不是进行 26 次往返。

它在表中插入了 10 行,然后又插入了 10 行,最后插入了剩下的 5 行。提交后,它又插入了一条记录以检索所有记录。
因此,它尽可能将其分成十个组。
因此,如果您要进行大量插入,这可以显著提高应用程序中的插入性能,因为您可以对其进行批处理。
这是因为 NHibernate 使用 guid.comb 算法自行分配这些 guid,并且它不必依赖数据库来执行此操作。
因此,使用批处理大小是一种很好的调整方法。
NHibernate - 缓存
在本章中,我们将介绍缓存在 NHibernate 应用程序中的工作原理。它内置了对缓存的支持。它看起来是一个简单的功能,但实际上,它是最复杂的功能之一。我们将从一级缓存开始。
一级缓存
此缓存机制在 NHibernate 中默认启用,我们无需执行任何操作即可使用缓存。为了理解这一点,让我们看一个简单的示例,正如您所见,我们的数据库中有两条记录。

现在在这个例子中,我们将检索 ID 为 1 的学生,我们将使用相同的会话查询两次,如下面的代码所示。
using HibernatingRhinos.Profiler.Appender.NHibernate; using NHibernate.Cache; using NHibernate.Cfg; using NHibernate.Dialect; using NHibernate.Driver; using NHibernate.Linq; using System; using System.Linq; using System.Reflection; namespace NHibernateDemoApp { class Program { static void Main(string[] args) { NHibernateProfiler.Initialize(); var cfg = new Configuration(); String Data Source = asia13797\sqlexpress; String Initial Catalog = NHibernateDemoDB; String Integrated Security = True; String Connect Timeout = 15; String Encrypt = False; String TrustServerCertificate = False; String ApplicationIntent = ReadWrite; String MultiSubnetFailover = False; cfg.DataBaseIntegration(x = > { x.ConnectionString = "Data Source + Initial Catalog + Integrated Security + Connect Timeout + Encrypt + TrustServerCertificate + ApplicationIntent + MultiSubnetFailover"; x.Driver<SqlClientDriver>(); x.Dialect<MsSql2008Dialect>(); x.LogSqlInConsole = true; x.BatchSize = 10; }); //cfg.Configure(); cfg.Cache(c => { c.UseMinimalPuts = true; c.UseQueryCache = true; }); cfg.SessionFactory().Caching .Through<HashtableCacheProvider>() .WithDefaultExpiration(1440); cfg.AddAssembly(Assembly.GetExecutingAssembly()); var sefact = cfg.BuildSessionFactory(); using (var session = sefact.OpenSession()){ using (var tx = session.BeginTransaction()) { var studentUsingTheFirstQuery = session.Get<Student>(1); var studentUsingTheSecondQuery = session.Get<Student>(1); } Console.ReadLine(); } } } }
现在让我们运行此应用程序并在 NHibernate Profiler 中查看结果。

您会惊讶地发现 NHibernate 只触发了一个查询。这就是 NHibernate 使用一级缓存的方式。执行第一个查询时,NHibernate 会将 ID = 1 的学生缓存在其一级缓存中。
因此,执行第二个查询时,NHibernate 首先查找 ID = 1 的一级缓存学生实体,如果找到该实体,则 NHibernate 知道,无需再次触发另一个查询来检索相同的员工对象。
NHibernate - 映射组件
在本章中,我们将讨论映射组件。在 NHibernate 中,组件是一个值对象。它没有自己的身份。
一个例子是金钱对象,钱包里可能有钱,但钱的具体身份无关紧要。
它没有自己的主键,但组件本身与拥有对象在同一张表中持久化。
让我们看一个简单的例子,其中一个学生有一个地址,它是与其关联的Location 类的对象。
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace NHibernateDemoApp { class Student { public virtual int ID { get; set; } public virtual string LastName { get; set; } public virtual string FirstName { get; set; } public virtual StudentAcademicStanding AcademicStanding { get; set; } public virtual Location Address { get; set; } } 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 StudentAcademicStanding { Excellent, Good, Fair, Poor, Terrible } }
现在,我们还需要通过执行以下查询来更新数据库,该查询将首先删除 Student 表,然后创建一个新表,该表还将包含 Location 类的列。
DROP TABLE [dbo].[Student] CREATE TABLE [dbo].[Student] ( [ID] INT IDENTITY (1, 1) NOT NULL, [LastName] NVARCHAR (MAX) NULL, [FirstMidName] NVARCHAR (MAX) NULL, [AcademicStanding] NCHAR(10) NULL, [Street] NVARCHAR (100) NULL, [City] NVARCHAR (100) NULL, [Province] NVARCHAR (100) NULL, [Country] NVARCHAR (100) NULL, CONSTRAINT [PK_dbo.Student] PRIMARY KEY CLUSTERED ([ID] ASC) );
现在映射那些不直接属于 Student 类的列,但它们是 Location 类的属性,并且 Location 类对象在 Student 类中定义。我们需要一个组件来正确映射它。让我们在 student.hbm.xml 文件中创建一个组件,如以下代码所示。
<?xml version = "1.0" encoding = "utf-8" ?> <hibernate-mapping xmlns = "urn:nhibernate-mapping-2.2" assembly = "NHibernateDemoApp" namespace = "NHibernateDemoApp"> <class name = "Student"> <id name = "ID"> <generator class = "native"/> </id> <property name = "LastName"/> <property name = "FirstName" column = "FirstMidName" type = "String"/> <property name = "AcademicStanding"/> <component name = "Address"> <property name = "Street"/> <property name = "City"/> <property name = "Province"/> <property name = "Country"/> </component> </class> </hibernate-mapping>
此组件是 Address,它具有这些不同的属性。有了这些信息,NHibernate 现在就可以真正映射它了。
现在这里是 Program.cs 文件,其中创建并初始化了一个新的学生对象,然后将其保存到数据库。然后它将从数据库中检索列表。
using HibernatingRhinos.Profiler.Appender.NHibernate; using NHibernate.Cache; using NHibernate.Caches.SysCache; using NHibernate.Cfg; using NHibernate.Dialect; using NHibernate.Driver; using NHibernate.Linq; using System; using System.Linq; using System.Reflection; namespace NHibernateDemoApp { class Program { static void Main(string[] args) { NHibernateProfiler.Initialize(); var cfg = new Configuration(); String Data Source = asia13797\sqlexpress; String Initial Catalog = NHibernateDemoDB; String Integrated Security = True; String Connect Timeout = 15; String Encrypt = False; String TrustServerCertificate = False; String ApplicationIntent = ReadWrite; String MultiSubnetFailover = False; cfg.DataBaseIntegration(x = > { x.ConnectionString = "Data Source + Initial Catalog + Integrated Security + Connect Timeout + Encrypt + TrustServerCertificate + ApplicationIntent + MultiSubnetFailover"; x.Driver<SqlClientDriver>(); x.Dialect<MsSql2008Dialect>(); }); cfg.AddAssembly(Assembly.GetExecutingAssembly()); var sefact = cfg.BuildSessionFactory(); using (var session = sefact.OpenSession()) { using (var tx = session.BeginTransaction()) { var student1 = new Student { ID = 1, FirstName = "Allan", LastName = "Bommer", AcademicStanding = StudentAcademicStanding.Poor, Address = new Location { Street = "123 Street", City = "Lahore", Province = "Punjab", Country = "Pakistan" } }; session.Save(student1); tx.Commit(); var students = session.Query<Student>().ToList<Student>(); Console.WriteLine(" Fetch the complete list again "); foreach (var student in students) { Console.WriteLine("{0} {1} {2} {3} {4} {5} {6} {7}", student.ID, student.FirstName, student.LastName, student.AcademicStanding, student.Address.Street, student.Address.City, student.Address.Province, student.Address.Country ); } } Console.ReadLine(); } } } }
现在我们可以运行此应用程序,NHibernate 可以将这些值保存到数据库中。运行应用程序时,您将看到以下输出。
Fetch the complete list again 2 Allan Bommer Poor 123 Street Lahore Punjab Pakistan
以下是数据库中的值。

这些组件允许我们将数据库表中的列分离到它们自己的单独类中。
这里要注意的另一件事是,Location 是一个类,而不是实体。
它是一个值类型对象,没有自己的主键。
它与包含它的学生保存在同一个表中。
这就是我们在这里使用组件的原因。
这允许我们非常灵活地更改我们的类层,我们的类的定义方式与数据库的布局方式。
NHibernate - 关系
在本章中,我们将研究 NHibernate 中的关系。让我们将注意力转向如何理解 NHibernate 中的关系。最简单的方法是从数据库的角度来思考关系。
我们将首先创建一个新的应用程序,在其中创建客户和订单实体之间的一些关系。
我们要研究的第一个关系是经典的集合关系。
我们有一个客户和一组订单。
这是一种一对多关系,它在数据库中由 2 个表表示,订单表上有一个客户 ID,我们有一个指向客户的外键关系。
首先,我们需要创建一个数据库和两个表 Customer 和 Order。您可以通过在 SQL Server Explorer 中指定以下查询来创建它。
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
它将在数据库中创建两个表。下图显示了客户表。

下图显示了订单表,您可以在其中看到与客户的外键关系。

我们需要在 app.config 文件中定义连接字符串,这里是 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>
要在您的应用程序中安装 NHibernate,请在 NuGet 管理器控制台窗口中运行以下命令。
install-package NHibernate
要配置 NHibernate 配置,我们需要在 hibernate.cfg.xml 文件中定义配置,如以下代码所示。
<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>
在此示例中,我们将使用两个域类,Customer 和 Order。
以下是 Customer.cs 文件实现,其中有两个类,一个是 Customer 类,另一个是 Location 类,其中对象在 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}) Points: {3} HasGoldStatus: {4} MemberSince: {5} ({7}) CreditRating: {6} AverageRating: {8} ", Id, FirstName, LastName, Points, HasGoldStatus, MemberSince, CreditRating, MemberSince.Kind, AverageRating); result.AppendLine(" Orders:"); foreach(var order in Orders) { result.AppendLine(" " + 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 } }
这是映射文件 Customer.hbm.xml,其中 Customer 类映射到 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>
我们还有一个 Order 类,这里是 Order.cs 文件的实现。
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); } } }
多对一关系
我们还需要将 Order 类映射到数据库中的 Order 表,因此这里是 Order.hbm.xml 文件的实现。
<?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>
一对多关系
在这里,我们将研究一对多关系,在本例中,是客户和订单之间的关系。我们这里有客户,我们正在创建一个新的客户,您可以看到集合已使用以下一对订单初始化。
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; }
因此我们将创建一个新客户,然后保存它,保存后,我们将找到 ID,然后在 Main 方法中的另一个会话中重新加载它,如下面的程序所示。
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(); }
以下是完整的 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 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; } } }
当您运行此应用程序时,您将看到以下输出。
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...
如您所见,最初客户有 2 个订单,但当我们重新加载时,看不到任何订单。如果您查看 customer.hbm.xml 文件,您会发现这里我们没有映射实际订单集合。因此 NHibernate 对此一无所知。让我们继续添加它。
<?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>
这是一个集合,这个集合的名称是"Orders",它存储在一个名为 order 的表中。我们需要指定一个键,它是外键的名称或查找订单。这些订单通过客户 ID 来识别或属于客户。然后我必须注意,这是一个一对多关系,它与 order 类有关。
我们还需要稍微更改 Main 方法,将新客户订单保存到数据库,如下面的程序所示。
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(); }
我们还指定了哪位客户订购了该特定产品。因此我们需要创建多对一关系以将该订单与该客户关联起来。
因此让我们进入 Order.hbm.xml 文件并添加多对一关系,然后使用客户 ID 命名客户字段和列。
<?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>
让我们再次运行此应用程序,现在您将看到以下输出。
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...
NHibernate - 集合映射
在本章中,我们将介绍如何表示集合。我们可以在 NHibernate 中使用不同类型的集合,例如 −
- 列表
- 集合
- Bag
现在,从 .NET 的角度来看,我们通常处理列表或非常简单的数据结构、列表、字典。.NET 没有各种不同的集合类型。那么为什么 NHibernate 需要所有这些不同类型?它实际上回到了数据库。
列表
列表是元素的有序集合,这些元素不一定是唯一的。
我们可以使用 IList <T> 映射它。
因此,尽管我们可能通常有一个地址列表,并且从应用程序的角度来看,我们知道元素是唯一的,但列表中没有任何内容可以阻止我们在该列表中插入重复元素。
集合
集合是唯一元素的无序集合。如果您尝试将 2 个重复元素插入集合中,它将引发异常。
NHibernate 中对此没有任何具体规定。
这只是一种拥有通用集合实现的便捷方式。如果您使用的是 .NET 4,则可以使用新的 HashSet <T> 来表示这些,但在大多数 NHibernate 应用程序中,我们将其表示为 ISet。
它是无序的,如果您从数据库或订单列表中拉回地址列表,除非您输入特定的 Order by 子句,否则您不知道它们的顺序。
因此,一般来说,您从数据库中提取的数据是集合。
它们是无序元素的唯一集合。
Bag
我们将在数据库世界中看到的另一个常见集合是 bag,它就像一个集合,但它可以有重复的元素。
在 .NET 世界中,我们将其表示为通过 IList。
集合可能是最常见的,但根据您的应用程序,您也会看到列表和包。让我们看一下上一章中的以下 customer.hbm.xml 文件,其中定义了 Set 顺序。
<?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>
如您所见,我们已将订单集合映射为一个集合。请记住,集合是唯一元素的无序集合。
现在,如果您查看 Customer 类,您将看到 Orders 属性使用 ISet 定义,如以下程序所示。
public virtual ISet<Order> Orders { get; set; }
现在,当运行此应用程序时,您将看到以下输出。
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 (1f248133-b50a-4ad7-9915-a5b8017d0ff1) Points: 100 HasGoldStatus: True MemberSince: 1/1/2012 12:00:00 AM (Utc) CreditRating: Good AverageRating: 42.4242 Orders: Order Id: c41af8f2-7124-42a7-91c5-a5b8017d0ff6 Order Id: 657f6bb0-1f42-45fc-8fc7-a5b8017d0ff7 The orders were ordered by: John Doe (1f248133-b50a-4ad7-9915-a5b8017d0ff1) Points: 100 HasGoldStatus: True MemberSince: 1/1/2012 12:00:00 AM (Utc) CreditRating: Good AverageRating: 42.4242 Orders: Order Id: c41af8f2-7124-42a7-91c5-a5b8017d0ff6 Order Id: 657f6bb0-1f42-45fc-8fc7-a5b8017d0ff7 John Doe (1f248133-b50a-4ad7-9915-a5b8017d0ff1) Points: 100 HasGoldStatus: True MemberSince: 1/1/2012 12:00:00 AM (Utc) CreditRating: Good AverageRating: 42.4242 Orders: Order Id: c41af8f2-7124-42a7-91c5-a5b8017d0ff6 Order Id: 657f6bb0-1f42-45fc-8fc7-a5b8017d0ff7 Press <ENTER> to exit...
如果集合中的项目不需要唯一,如果您可以有多个订单,并且同一个主键在此集合中多次出现,那么最好将其映射为一个包,如下面的程序所示。
<bag name = "Orders" table = "`Order`"> <key column = "CustomerId"/> <one-to-many class = "Order"/> </bag>
现在,如果您运行此应用程序,您将收到一个异常,因为如果我们查看客户类,您会注意到订单在 C# 代码中被标记为 ISet。
因此,我们还需要将其更改为 IList,然后在这里,我们需要在构造函数中将 HashSet 更改为 List。
public class Customer { public Customer() { MemberSince = DateTime.UtcNow; Orders = new List<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 IList<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}) Points: {3} HasGoldStatus: {4} MemberSince: {5} ({7}) CreditRating: {6} AverageRating: {8} ", Id, FirstName, LastName, Points, HasGoldStatus, MemberSince, CreditRating, MemberSince.Kind, AverageRating); result.AppendLine(" Orders:"); foreach(var order in Orders) { result.AppendLine(" " + order); } return result.ToString(); } }
运行应用程序时,您将看到相同的行为。但是,现在我们可以在同一个集合中多次出现一个订单。
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 (fbde48f5-d620-4d1c-9a7f-a5b8017c3280) Points: 100 HasGoldStatus: True MemberSince: 1/1/2012 12:00:00 AM (Utc) CreditRating: Good AverageRating: 42.4242 Orders: Order Id: 6dd7dbdb-354f-4c82-9c39-a5b8017c3286 Order Id: 9b3e2441-a81b-404d-9aed-a5b8017c3287 The orders were ordered by: John Doe (fbde48f5-d620-4d1c-9a7f-a5b8017c3280) Points: 100 HasGoldStatus: True MemberSince: 1/1/2012 12:00:00 AM (Utc) CreditRating: Good AverageRating: 42.4242 Orders: Order Id: 6dd7dbdb-354f-4c82-9c39-a5b8017c3286 Order Id: 9b3e2441-a81b-404d-9aed-a5b8017c3287 John Doe (fbde48f5-d620-4d1c-9a7f-a5b8017c3280) Points: 100 HasGoldStatus: True MemberSince: 1/1/2012 12:00:00 AM (Utc) CreditRating: Good AverageRating: 42.4242 Orders: Order Id: 6dd7dbdb-354f-4c82-9c39-a5b8017c3286 Order Id: 9b3e2441-a81b-404d-9aed-a5b8017c3287 Press <ENTER> to exit...
NHibernate - Cascades
在本章中,我们将介绍如何使用 Cascade 功能。如果您有一组或一组项目,或者两个类之间的关系(例如我们的客户和订单),并且具有外键关系。如果我们默认删除客户,NHibernate 不会对子对象执行任何操作,因此属于该客户和我们的子对象可能会成为孤立订单。
我们还可能违反外键约束,因此我们可以使用级联的概念。
默认情况下,NHibernate 不会将操作级联到子对象。
这样做的原因是您可以拥有诸如客户具有默认送货地址并且该送货地址与许多不同客户共享的关系。
因此,您不一定希望级联该关系,因为其他客户仍在引用它。
因此,级联的整个概念是告诉 NHibernate 如何处理其子实体。
级联有不同的选项,如下所示−
none − 为默认值,表示无级联。
all − 将级联保存、更新和删除。
save-update − 将级联保存和更新。
delete − 将级联删除。
all-delete-orphan −它是一种非常常用的特殊方法,与 All Except 相同,如果找到 Delete-orphan 行,它也会删除这些行。
您可以在 hbm.xml 文件中指定默认值,因此您可以在该 Hibernate 映射元素上提供默认级联,也可以为特定集合和关系(如多对一)指定它。
让我们看一个简单的级联示例,让我们修复程序中的问题,我们必须手动将保存级联到订单,如下面的代码所示。
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 newCustomer = CreateCustomer(); Console.WriteLine("New Customer:"); Console.WriteLine(newCustomer); session.Save(newCustomer); id = newCustomer.Id; tx.Commit(); }
我们需要在 customer.hbm.xml 中指定级联选项。
<?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"> <key column = "CustomerId"/> <one-to-many class = "Order"/> </set> </class> </hibernate-mapping>
现在,订单完全属于客户。因此,如果客户从数据库中删除,我们的应用程序将删除所有这些订单,包括任何可能已被遗弃的订单。
它最终会执行删除操作。这样,它将从订单表中删除,其中客户 ID 等于您要删除的客户。
因此,您实际上可以级联这些删除。因此,使用 All,它将执行保存、更新和删除。
现在,当您运行此应用程序时,您将看到以下输出。
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 (10b2a3d7-7fcf-483c-b1da-a5bb00b8512e) Points: 100 HasGoldStatus: True MemberSince: 1/1/2012 12:00:00 AM (Utc) CreditRating: Good AverageRating: 42.4242 Orders: Order Id: e6680e30-5b3b-4efa-b017-a5bb00b85133 Order Id: b03858e7-8c36-4555-8878-a5bb00b85134 The orders were ordered by: John Doe (10b2a3d7-7fcf-483c-b1da-a5bb00b8512e) Points: 100 HasGoldStatus: True MemberSince: 1/1/2012 12:00:00 AM (Utc) CreditRating: Good AverageRating: 42.4242 Orders: Order Id: e6680e30-5b3b-4efa-b017-a5bb00b85133 Order Id: b03858e7-8c36-4555-8878-a5bb00b85134 John Doe (10b2a3d7-7fcf-483c-b1da-a5bb00b8512e) Points: 100 HasGoldStatus: True MemberSince: 1/1/2012 12:00:00 AM (Utc) CreditRating: Good AverageRating: 42.4242 Orders: Order Id: e6680e30-5b3b-4efa-b017-a5bb00b85133 Order Id: b03858e7-8c36-4555-8878-a5bb00b85134 Press <ENTER> to exit...
如您所见,我们已经从程序中删除了手动级联的代码,并且我们的应用程序仍在运行。
因此,根据您的关系,您可能希望级联它们。现在,让我们看一下不同的级联关系。让我们转到 Order.hbm.xml 文件,我们可以级联该多对一关系。
<?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>
因此,如果我们创建一个新订单,并且该订单附有新客户,并且我们说保存该订单,我们可能希望将其级联。但是,我们可能不想做的一件事是,如果删除订单,则删除相应的客户。
因此,在这里,我们想要进行保存更新,因此使用保存更新,它将级联对该客户的任何保存或更新。因此,如果我们获得新客户或如果我们要更改客户,它将级联。如果是删除,它不会从数据库中删除。
因此,再次运行我们的应用程序,一切仍按预期运行。
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: Id: 00000000-0000-0000-0000-000000000000 Order Id: 00000000-0000-0000-0000-000000000000 Reloaded: John Doe (10b2a3d7-7fcf-483c-b1da-a5bb00b8512e) Points: 100 HasGoldStatus: True MemberSince: 1/1/2012 12:00:00 AM (Utc) CreditRating: Good AverageRating: 42.4242 Orders: Order Id: e6680e30-5b3b-4efa-b017-a5bb00b85133 Order Id: b03858e7-8c36-4555-8878-a5bb00b85134 The orders were ordered by: John Doe (10b2a3d7-7fcf-483c-b1da-a5bb00b8512e) Points: 100 HasGoldStatus: True MemberSince: 1/1/2012 12:00:00 AM (Utc) CreditRating: Good AverageRating: 42.4242 Orders: Order Id: e6680e30-5b3b-4efa-b017-a5bb00b85133 Order Id: b03858e7-8c36-4555-8878-a5bb00b85134 John Doe (10b2a3d7-7fcf-483c-b1da-a5bb00b8512e) Points: 100 HasGoldStatus: True MemberSince: 1/1/2012 12:00:00 AM (Utc) CreditRating: Good AverageRating: 42.4242 Orders: Order Id: e6680e30-5b3b-4efa-b017-a5bb00b85133 Order Id: b03858e7-8c36-4555-8878-a5bb00b85134 Press <ENTER> to exit...
现在您应该看一下您的应用程序,记住默认值为 None ,您必须考虑您的实体及其之间的关系,以确定每个实体的适当级联以及该数据库中的每个关系。
NHibernate - 延迟加载
在本章中,我们将介绍延迟加载功能。默认情况下,这是一个完全不同的概念,NHibernate 没有延迟加载,例如,如果您加载客户,它不会加载所有订单。
订单集合将按需加载。
任何关联,无论是多对一还是集合,默认情况下都是延迟加载的,它需要 Open ISession。
如果您已关闭会话,或者已提交事务,则可能会出现延迟加载异常,表明无法提取这些其他对象。
您必须小心延迟加载以及您实际需要的数据量。
您可以关闭整个关联的延迟加载,也可以将 lazy equals 设置为 false,或者也可以指定获取策略。
这是 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 reloaded = session.Load<Customer>(id); Console.WriteLine("Reloaded:"); Console.WriteLine(reloaded); 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(); } 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。

正如您所见,我们有 Select From Customer,给定一个特定的客户 ID,然后我们还有另一个 Select From Orders 表,当它实际访问该客户的集合时。
因此,我们有 2 次往返数据库。现在,有时,我们想要优化这一点。为此,让我们转到 customer.hbm.xml 文件并添加一个获取策略并要求它执行连接获取。
<?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" fetch = "join"> <key column = "CustomerId"/> <one-to-many class = "Order"/> </set> </class> </hibernate-mapping>
如您所见,我们没有更改应用程序中的任何代码,我们只是在 customer.hbm.xml 中添加了一个获取策略。让我们再次运行此应用程序,它仍然表现得完全相同。让我们看看 NHibernate Profiler。

以前,程序需要两次往返数据库,现在只需要一次,这是因为它在这里执行左外连接。
我们可以看到它根据客户 ID 在客户表和订单表之间执行左外连接,因此,它能够一次加载所有信息。
我们节省了一次往返数据库的次数。
缺点是客户信息将在两行上重复,这就是 SQL 左外连接的工作方式。
因此,通过获取策略,我们可以拉回更多数据,并节省往返。
您也可以在查询级别执行此操作,因此让我们转到 Program.cs 文件并查看更简单的重新加载示例。
using(var session = sessionFactory.OpenSession()) using(var tx = session.BeginTransaction()) { //var query = from customer in session.Query<Customer>() // select customer; //var reloaded = query.Fetch(x => x.Orders).ToList(); var reloaded = session.Load<Customer>(id); Console.WriteLine("Reloaded:"); Console.WriteLine(reloaded); Console.WriteLine("The orders were ordered by: "); foreach (var order in reloaded.Orders) { Console.WriteLine(order.Customer); } 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(); }
我们还可以从 customer.hbm.xml 文件中删除获取策略。
<?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"> <key column = "CustomerId"/> <one-to-many class = "Order"/> </set> </class> </hibernate-mapping>
让我们再次运行该应用程序,您将看到以下输出。
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 (6ebacd17-f9ba-4ad8-9817-a5bb01112a5a) Points: 100 HasGoldStatus: True MemberSince: 1/1/2012 12:00:00 AM (Utc) CreditRating: Good AverageRating: 42.4242 Orders: Order Id: 16a6596b-d56e-41c7-9681-a5bb01112a60 Order Id: d41d615b-0f21-4032-81db-a5bb01112a61 Press <ENTER> to exit...
现在让我们看看 NHibernate Profiler,你可以看到我们再次发生了这种急切的连接获取,但这一次,它是基于查询的。

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}) Points: {3} HasGoldStatus: {4} MemberSince: {5} ({7}) CreditRating: {6} AverageRating: {8} ", Id, FirstName, LastName, Points, HasGoldStatus, MemberSince, CreditRating, MemberSince.Kind, AverageRating); result.AppendLine(" Orders:"); foreach(var order in Orders) { result.AppendLine(" " + 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,因此请设置该外键。最后一条语句是更新,它将再次将外键更新为相同的客户 ID。

现在的问题是客户有订单,订单有客户,我们不可能没有告诉 NHibernate 这实际上是相同的关系。我们这样做的方法是使用 inverse 等于 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 分析器。

如果我们查看这些内容的插入方式,我们会发现插入到客户中,插入到订单中,但是我们没有外键的重复更新,因为它是在保存订单时更新的。
现在,您应该注意,如果您只有单向关联,并且是维持这种关系的集合,那么如果您将逆设置为 true,则永远不会设置该外键,并且这些项目永远不会在数据库中设置其外键。
如果您查看 Order.hbm.xml 文件中的多对一关系并查找逆,它实际上没有逆属性。
它始终从子项设置,但如果您有多对多集合,则可以从任一侧设置它。
NHibernate - Load/Get
在本章中,我们将介绍 Load 和 Get 功能的工作原理以及如何使用它们。这是 ISession 提供的两个非常相似的 API,用于通过主键加载对象。
Get − 它将返回对象或 null。
Load −它将返回对象,否则将抛出 ObjectNotFoundException。
现在,为什么我们有这两个不同的 API?
Load
这是因为 Load 可以更有效地优化数据库往返。
Load 实际上返回一个代理对象,并且在您发出该 Load 调用时不需要访问数据库。
当您访问该代理时,该对象恰好不在数据库中,它可以在此时抛出 ObjectNotFoundException。
Get
相反,由于 CLR 或 Common Language Runtime 和 NHibernate 的限制,Get 必须立即转到数据库,检查对象是否存在并返回 null,如果不存在存在。
它没有延迟该提取的对象选项,该往返数据库的时间稍后,因为它无法返回代理对象,并且当用户实际访问它时,该代理对象被替换为空。
让我们看一个简单的例子,您将在其中看到它们实际上是如何使用的,以及 Get 和 Load 之间的区别。我们将继续使用相同的域类 Customers 和 Orders 以及上一章中的相同映射文件。
在此示例中,我们将首先使用 Get,如以下程序所示。
using System; using System.Data; using System.Linq; using System.Reflection; using HibernatingRhinos.Profiler.Appender.NHibernate; using NHibernate.Cfg; using NHibernate.Criterion; 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(); using(var session = sessionFactory.OpenSession()) using(var tx = session.BeginTransaction()) { var id1 = Guid.Parse("4e97c816-6bce-11e1-b095-6cf049ee52be"); var id2 = Guid.Parse("AAAAAAAA-BBBB-CCCC-DDDD-EEEEEEEEEEEE"); var customer1 = session.Get<Customer>(id1); Console.WriteLine("Customer1 data"); Console.WriteLine(customer1); var customer2 = session.Get<Customer>(id2); Console.WriteLine("Customer2 data"); Console.WriteLine(customer2); tx.Commit(); } Console.WriteLine("Press <ENTER> to exit..."); Console.ReadLine(); } 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; } } }
如您所见,我们有两个 Guid ID,第一个是好 ID,它是我们知道在数据库中的客户的 ID。而第二个 ID 不存在于数据库中。这两个 ID 都作为参数传递给 Get() 方法,然后将结果打印在控制台上。
编译并执行上述代码后,您将看到以下输出。
Customer1 data Laverne Hegmann (4e97c816-6bce-11e1-b095-6cf049ee52be) Points: 74 HasGoldStatus: True MemberSince: 4/4/2009 12:00:00 AM (Utc) CreditRating: Neutral AverageRating: 0 Orders: Order Id: 4ea14d96-6bce-11e1-b095-6cf049ee52be Order Id: 4ea14d96-6bce-11e1-b096-6cf049ee52be Order Id: 4ea14d96-6bce-11e1-b097-6cf049ee52be Order Id: 4ea14d96-6bce-11e1-b098-6cf049ee52be Customer2 data Press <ENTER> to exit...
如您所见,Customer1 数据已打印,但 Customer2 数据为空,这是因为数据库中没有 Customer2 记录。
再次运行应用程序时,我们可以在提交语句之前插入一个断点,然后让我们在 Watch 窗口中查看两个客户。

如您所见,Customer1 数据可用,而 Customer2 为空,并且两者的类型均为 NHibernateDemo.Customer。
现在让我们在同一个示例中使用 Load 方法代替 Get,如以下代码所示。
using System; using System.Data; using System.Linq; using System.Reflection; using HibernatingRhinos.Profiler.Appender.NHibernate; using NHibernate.Cfg; using NHibernate.Criterion; 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(); using(var session = sessionFactory.OpenSession()) using(var tx = session.BeginTransaction()) { var id1 = Guid.Parse("4e97c816-6bce-11e1-b095-6cf049ee52be"); var id2 = Guid.Parse("AAAAAAAA-BBBB-CCCC-DDDD-EEEEEEEEEEEE"); var customer1 = session.Load<Customer>(id1); Console.WriteLine("Customer1 data"); Console.WriteLine(customer1); var customer2 = session.Load<Customer>(id2); Console.WriteLine("Customer2 data"); Console.WriteLine(customer2); tx.Commit(); } Console.WriteLine("Press <ENTER> to exit..."); Console.ReadLine(); } 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; } } }
现在让我们运行这个示例,您将看到屏幕截图中抛出了以下异常。

现在,如果您查看 Watch 窗口,您将看到两个对象的类型都是客户代理。并且您还会在控制台窗口中看到 Customer1 的相同数据。
Customer1 data Laverne Hegmann (4e97c816-6bce-11e1-b095-6cf049ee52be) Points: 74 HasGoldStatus: True MemberSince: 4/4/2009 12:00:00 AM (Utc) CreditRating: Neutral AverageRating: 0 Orders: Order Id: 4ea14d96-6bce-11e1-b095-6cf049ee52be Order Id: 4ea14d96-6bce-11e1-b096-6cf049ee52be Order Id: 4ea14d96-6bce-11e1-b097-6cf049ee52be Order Id: 4ea14d96-6bce-11e1-b098-6cf049ee52be Customer2 data
NHibernate - Linq
在本章中,我们将介绍人们会使用的另一个常见 API,即 NHibernate LINQ 提供程序。它通过 ISession 上的扩展方法进行访问,签名是 Query <T>。使用 LINQ 时有两种类型的语法 −
- 查询链语法
- 查询理解语法
查询链语法
您可以使用方法链语法从数据库访问任何记录,如以下程序所示。
var customer = session.Query<Customer>() .Where(c => c.FirstName == "Laverne")
您可以看到我们有查询,还有 WHERE 子句,您可以有额外的 WHERE 子句和类似的 select 子句。
这是您可以在普通 LINQ 中使用的标准方法链语法。
LINQ to Objects 或 LINQ to SQL,任何其他您可能熟悉的 LINQ 提供程序。
让我们看一个简单的示例,其中我们将检索名字为 Laverne 的客户。现在我们可能有多个名字为 Laverne 的客户,因此我们将仅检索第一个客户。
using System; using System.Data; using System.Linq; using System.Reflection; using HibernatingRhinos.Profiler.Appender.NHibernate; using NHibernate.Cfg; using NHibernate.Criterion; 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(); using(var session = sessionFactory.OpenSession()) using(var tx = session.BeginTransaction()) { var customer = session.Query<Customer>() .Where(c => c.FirstName == "Laverne").First(); Console.WriteLine(customer); tx.Commit(); } Console.WriteLine("Press <ENTER> to exit..."); Console.ReadLine(); } 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; } } }
现在,当编译并执行上述代码时,您将看到以下输出。
Laverne Hegmann (4e97c816-6bce-11e1-b095-6cf049ee52be) Points: 74 HasGoldStatus: True MemberSince: 4/4/2009 12:00:00 AM (Utc) CreditRating: Neutral AverageRating: 0 Orders: Order Id: 4ea14d96-6bce-11e1-b095-6cf049ee52be Order Id: 4ea14d96-6bce-11e1-b096-6cf049ee52be Order Id: 4ea14d96-6bce-11e1-b097-6cf049ee52be Order Id: 4ea14d96-6bce-11e1-b098-6cf049ee52be Press <ENTER> to exit...
查询理解语法
还有查询理解语法,它看起来更像使用 from、where 和 select 关键字的 SQL。
让我们看一下同一个示例,但这次我们使用 LINQ 理解语法,它看起来更像 SQL,如以下程序所示。
using System; using System.Data; using System.Linq; using System.Reflection; using HibernatingRhinos.Profiler.Appender.NHibernate; using NHibernate.Cfg; using NHibernate.Criterion; 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(); using(var session = sessionFactory.OpenSession()) using(var tx = session.BeginTransaction()) { var customer = (from c in session.Query<Customer>() where c.FirstName == "Laverne" select c).First(); Console.WriteLine(customer); tx.Commit(); } Console.WriteLine("Press <ENTER> to exit..."); Console.ReadLine(); } 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; } } }
现在让我们再次运行此应用程序,您将看到以下输出。
Laverne Hegmann (4e97c816-6bce-11e1-b095-6cf049ee52be) Points: 74 HasGoldStatus: True MemberSince: 4/4/2009 12:00:00 AM (Utc) CreditRating: Neutral AverageRating: 0 Orders: Order Id: 4ea14d96-6bce-11e1-b095-6cf049ee52be Order Id: 4ea14d96-6bce-11e1-b096-6cf049ee52be Order Id: 4ea14d96-6bce-11e1-b097-6cf049ee52be Order Id: 4ea14d96-6bce-11e1-b098-6cf049ee52be Press <ENTER> to exit...
让我们看另一个示例,其中我们将检索所有 FirstName 以字母 H 开头的客户。
using System; using System.Data; using System.Linq; using System.Reflection; using HibernatingRhinos.Profiler.Appender.NHibernate; using NHibernate.Cfg; using NHibernate.Criterion; 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(); using(var session = sessionFactory.OpenSession()) using(var tx = session.BeginTransaction()) { var customers = session.Query<Customer>() .Where(c =< c.FirstName.StartsWith("H")); foreach (var customer in customers.ToList()) { Console.WriteLine(customer); } tx.Commit(); } Console.WriteLine("Press <ENTER> to exit..."); Console.ReadLine(); } 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; } } }
类似地,查询理解语法将类似于以下程序。
using System; using System.Data; using System.Linq; using System.Reflection; using HibernatingRhinos.Profiler.Appender.NHibernate; using NHibernate.Cfg; using NHibernate.Criterion; 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(); using(var session = sessionFactory.OpenSession()) using(var tx = session.BeginTransaction()) { var customers = from c in session.Query<Customer>() where c.FirstName.StartsWith("H") select c; foreach (var customer in customers.ToList()) { Console.WriteLine(customer); } tx.Commit(); } Console.WriteLine("Press <ENTER> to exit..."); Console.ReadLine(); } 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; } } }
让我们再次运行此应用程序,您将看到所有名字以字母 H 开头的客户。
Herman Crooks (4ead3480-6bce-11e1-b15c-6cf049ee52be) Points: 74 HasGoldStatus: True MemberSince: 12/3/2010 12:00:00 AM (Utc) CreditRating: Neutral AverageRating: 0 Orders: Order Id: 4ead3480-6bce-11e1-b15d-6cf049ee52be Order Id: 4ead3480-6bce-11e1-b15e-6cf049ee52be Order Id: 4ead3480-6bce-11e1-b15f-6cf049ee52be Order Id: 4ead3480-6bce-11e1-b160-6cf049ee52be Order Id: 4ead3480-6bce-11e1-b161-6cf049ee52be Order Id: 4ead3480-6bce-11e1-b162-6cf049ee52be Order Id: 4ead3480-6bce-11e1-b163-6cf049ee52be Hudson Bins (4ec03f80-6bce-11e1-b2b7-6cf049ee52be) Points: 56 HasGoldStatus: False MemberSince: 10/20/2008 12:00:00 AM (Utc) CreditRating: Terrible AverageRating: 0 Orders: Order Id: 4ec03f80-6bce-11e1-b2b8-6cf049ee52be Order Id: 4ec03f80-6bce-11e1-b2b9-6cf049ee52be Order Id: 4ec03f80-6bce-11e1-b2ba-6cf049ee52be Order Id: 4ec03f80-6bce-11e1-b2bb-6cf049ee52be Order Id: 4ec03f80-6bce-11e1-b2bc-6cf049ee52be Order Id: 4ec03f80-6bce-11e1-b2bd-6cf049ee52be Order Id: 4ec03f80-6bce-11e1-b2be-6cf049ee52be Order Id: 4ec03f80-6bce-11e1-b2bf-6cf049ee52be Hettie Feest (4ec50240-6bce-11e1-b300-6cf049ee52be) Points: 82 HasGoldStatus: False MemberSince: 4/10/2009 12:00:00 AM (Utc) CreditRating: Neutral AverageRating: 0 Orders: Order Id: 4ec50240-6bce-11e1-b301-6cf049ee52be Order Id: 4ec50240-6bce-11e1-b302-6cf049ee52be Order Id: 4ec50240-6bce-11e1-b303-6cf049ee52be Press <ENTER> to exit...
NHibernate - Hibernate 查询语言
在本章中,我们将介绍 Hibernate 查询语言。 HQL 在 Java 的 Hibernate 和 NHibernate 中是共享的。
它是与 Criteria 一起最古老的查询机制。
它很早就实现了,是一个基于字符串的查询 API。
您可以通过 ISession CreateQuery 访问它,它几乎与 SQL 相似。
它使用许多相同的关键字,但语法简化了。
它是最常见的示例之一,如果您正在寻找如何执行查询,您经常会找到 HQL 示例。
以下是 HQL − 的简单示例
var customers = session.CreateQuery("select c from Customer c where c.FirstName = 'Laverne'");
所以在这里你可以看到他们从客户中选择了 C,它看起来很像 SQL。就 NHibernate 而言,这是一个不透明的字符串,因此你直到运行时才知道这是否是有效的 HQL,这是缺点之一。
LINQ 提供程序的优势之一是你可以获得编译时支持。
但是 HQL 是最灵活的查询机制之一,经常使用。据说,如果没有其他方法可以做到这一点,那么就有一种方法可以在 HQL 中做到这一点。
让我们看一个简单的例子,在这个例子中,我们将使用 HQL 重新创建我们的 LINQ 查询。您可以通过调用 session.CreateQuery 来访问 HQL,并使用 HQL 字符串作为参数传递。
using System; using System.Data; using System.Linq; using System.Reflection; using HibernatingRhinos.Profiler.Appender.NHibernate; using NHibernate.Cfg; using NHibernate.Criterion; 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(); using(var session = sessionFactory.OpenSession()) using(var tx = session.BeginTransaction()) { var customers = session.CreateQuery("select c from Customer c where c.FirstName = 'Laverne'"); foreach (var customer in customers.List<Customer>()) { Console.WriteLine(customer); } tx.Commit(); } Console.WriteLine("Press <ENTER> to exit..."); Console.ReadLine(); } 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; } } }
此 HQL 字符串看起来很像 SQL,主要区别在于 FirstName 是属性名称而不是列名称。
因此,如果两者之间存在差异,则使用属性名称。同样,它看起来像表名,但实际上是我们从中选择的类的名称。
如果后端表名为 Customers,我们仍会在 HQL 查询中使用 Customer。
让我们运行此应用程序,您将看到以下输出。
Laverne Hegmann (4e97c816-6bce-11e1-b095-6cf049ee52be) Points: 74 HasGoldStatus: True MemberSince: 4/4/2009 12:00:00 AM (Utc) CreditRating: Neutral AverageRating: 0 Orders: Order Id: 4ea14d96-6bce-11e1-b095-6cf049ee52be Order Id: 4ea14d96-6bce-11e1-b096-6cf049ee52be Order Id: 4ea14d96-6bce-11e1-b097-6cf049ee52be Order Id: 4ea14d96-6bce-11e1-b098-6cf049ee52be Press <ENTER> to exit...
让我们看另一个简单的例子,我们将使用 HQL 检索 FirstName 以字母 H 开头的所有客户。
using System; using System.Data; using System.Linq; using System.Reflection; using HibernatingRhinos.Profiler.Appender.NHibernate; using NHibernate.Cfg; using NHibernate.Criterion; 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(); using(var session = sessionFactory.OpenSession()) using(var tx = session.BeginTransaction()) { var customers = session.CreateQuery("select c from Customer c where c.FirstName like 'H%'"); foreach (var customer in customers.List<Customer>()) { Console.WriteLine(customer); } tx.Commit(); } Console.WriteLine("Press <ENTER> to exit..."); Console.ReadLine(); } 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; } } }
让我们再次运行您的应用程序,您将看到此查询返回了所有名称以 H 开头的客户。
Herman Crooks (4ead3480-6bce-11e1-b15c-6cf049ee52be) Points: 74 HasGoldStatus: True MemberSince: 12/3/2010 12:00:00 AM (Utc) CreditRating: Neutral AverageRating: 0 Orders: Order Id: 4ead3480-6bce-11e1-b15d-6cf049ee52be Order Id: 4ead3480-6bce-11e1-b15e-6cf049ee52be Order Id: 4ead3480-6bce-11e1-b15f-6cf049ee52be Order Id: 4ead3480-6bce-11e1-b160-6cf049ee52be Order Id: 4ead3480-6bce-11e1-b161-6cf049ee52be Order Id: 4ead3480-6bce-11e1-b162-6cf049ee52be Order Id: 4ead3480-6bce-11e1-b163-6cf049ee52be Hudson Bins (4ec03f80-6bce-11e1-b2b7-6cf049ee52be) Points: 56 HasGoldStatus: False MemberSince: 10/20/2008 12:00:00 AM (Utc) CreditRating: Terrible AverageRating: 0 Orders: Order Id: 4ec03f80-6bce-11e1-b2b8-6cf049ee52be Order Id: 4ec03f80-6bce-11e1-b2b9-6cf049ee52be Order Id: 4ec03f80-6bce-11e1-b2ba-6cf049ee52be Order Id: 4ec03f80-6bce-11e1-b2bb-6cf049ee52be Order Id: 4ec03f80-6bce-11e1-b2bc-6cf049ee52be Order Id: 4ec03f80-6bce-11e1-b2bd-6cf049ee52be Order Id: 4ec03f80-6bce-11e1-b2be-6cf049ee52be Order Id: 4ec03f80-6bce-11e1-b2bf-6cf049ee52be Hettie Feest (4ec50240-6bce-11e1-b300-6cf049ee52be) Points: 82 HasGoldStatus: False MemberSince: 4/10/2009 12:00:00 AM (Utc) CreditRating: Neutral AverageRating: 0 Orders: Order Id: 4ec50240-6bce-11e1-b301-6cf049ee52be Order Id: 4ec50240-6bce-11e1-b302-6cf049ee52be Order Id: 4ec50240-6bce-11e1-b303-6cf049ee52be Press <ENTER> to exit...
我们可以做更复杂的事情,比如获取订单数量大于 9 的客户的所有订单。以下是相同的 HQL 查询。
var customers = session.CreateQuery("select c from Customer c where size(c.Orders) > 9"); foreach (var customer in customers.List<Customer>()) { Console.WriteLine(customer); }
我们还需要指出我们需要的是大小、数量还是长度。在 HQL 中,我们可以选择使用如上所示的特殊 size 方法。
如果您愿意,另一种编写方法是 c.Orders.size,这具有完全相同的效果。
var customers = session.CreateQuery("select c from Customer c where c.Orders.size > 9"); foreach (var customer in customers.List<Customer>()) { Console.WriteLine(customer); }
让我们运行这个应用程序。
Lindsay Towne (4ea3aef6-6bce-11e1-b0cb-6cf049ee52be) Points: 50 HasGoldStatus: False MemberSince: 4/13/2007 12:00:00 AM (Utc) CreditRating: VeryGood AverageRating: 0 Orders: Order Id: 4ea3aef6-6bce-11e1-b0cc-6cf049ee52be Order Id: 4ea3aef6-6bce-11e1-b0cd-6cf049ee52be Order Id: 4ea3aef6-6bce-11e1-b0ce-6cf049ee52be Order Id: 4ea3aef6-6bce-11e1-b0cf-6cf049ee52be Order Id: 4ea3aef6-6bce-11e1-b0d0-6cf049ee52be Order Id: 4ea3aef6-6bce-11e1-b0d1-6cf049ee52be Order Id: 4ea3aef6-6bce-11e1-b0d2-6cf049ee52be Order Id: 4ea3aef6-6bce-11e1-b0d3-6cf049ee52be Order Id: 4ea3aef6-6bce-11e1-b0d4-6cf049ee52be Order Id: 4ea3aef6-6bce-11e1-b0d5-6cf049ee52be Wyman Hammes (4ea61056-6bce-11e1-b0e2-6cf049ee52be) Points: 32 HasGoldStatus: False MemberSince: 2/5/2011 12:00:00 AM (Utc) CreditRating: Good AverageRating: 0 Orders: Order Id: 4ea61056-6bce-11e1-b0e3-6cf049ee52be Order Id: 4ea61056-6bce-11e1-b0e4-6cf049ee52be Order Id: 4ea61056-6bce-11e1-b0e5-6cf049ee52be Order Id: 4ea61056-6bce-11e1-b0e6-6cf049ee52be Order Id: 4ea61056-6bce-11e1-b0e7-6cf049ee52be Order Id: 4ea61056-6bce-11e1-b0e8-6cf049ee52be Order Id: 4ea61056-6bce-11e1-b0e9-6cf049ee52be Order Id: 4ea61056-6bce-11e1-b0ea-6cf049ee52be Order Id: 4ea61056-6bce-11e1-b0eb-6cf049ee52be Order Id: 4ea61056-6bce-11e1-b0ec-6cf049ee52be Press <ENTER> to exit...
您可以看到从数据库中检索了所有拥有超过 9 个订单的客户。
NHibernate - Criteria 查询
在本章中,我们将介绍条件查询机制。NHibernate Criteria 查询 API 允许您通过在运行时操作条件对象来构建查询。
此方法允许您动态指定约束,而无需直接进行字符串操作,但不会损失太多 HQL 的灵活性或功能。
另一方面,以条件表示的查询通常比以 HQL 表示的查询更难读。
经典的条件语法是基于对象的查询 API,如以下程序所示。
var customers = session.CreateCriteria<Customer>().Add(Restrictions.Like("FirstName", "H%"));
如您所见,我们正在对客户执行会话创建条件,现在我们正在向该查询添加限制对象。
这对于用户可以选择某些选项但不能选择其他选项的查询页面很有用。
将查询构建为树状查询结构比在 HQL 或 LINQ 中更容易,在 HQL 或 LINQ 中,您可以在 WHERE 子句中使用 AND 或 OR。
使用这些条件对象添加其他限制更容易。
让我们看一个简单的示例,我们将创建一个查询并通过 createCriteria 访问条件 API,然后添加一个限制,即名字以 H 开头。
using System; using System.Data; using System.Linq; using System.Reflection; using HibernatingRhinos.Profiler.Appender.NHibernate; using NHibernate.Cfg; using NHibernate.Criterion; 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(); using(var session = sessionFactory.OpenSession()) using(var tx = session.BeginTransaction()) { var customers = session.CreateCriteria<Customer>() .Add(Restrictions.Like("FirstName", "H%")); foreach (var customer in customers.List<Customer>()) { Console.WriteLine(customer); } tx.Commit(); } Console.WriteLine("Press <ENTER> to exit..."); Console.ReadLine(); } 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; } } }
当编译并执行上述代码时,您将看到以下输出。
Herman Crooks (4ead3480-6bce-11e1-b15c-6cf049ee52be) Points: 74 HasGoldStatus: True MemberSince: 12/3/2010 12:00:00 AM (Utc) CreditRating: Neutral AverageRating: 0 Orders: Order Id: 4ead3480-6bce-11e1-b15d-6cf049ee52be Order Id: 4ead3480-6bce-11e1-b15e-6cf049ee52be Order Id: 4ead3480-6bce-11e1-b15f-6cf049ee52be Order Id: 4ead3480-6bce-11e1-b160-6cf049ee52be Order Id: 4ead3480-6bce-11e1-b161-6cf049ee52be Order Id: 4ead3480-6bce-11e1-b162-6cf049ee52be Order Id: 4ead3480-6bce-11e1-b163-6cf049ee52be Hudson Bins (4ec03f80-6bce-11e1-b2b7-6cf049ee52be) Points: 56 HasGoldStatus: False MemberSince: 10/20/2008 12:00:00 AM (Utc) CreditRating: Terrible AverageRating: 0 Orders: Order Id: 4ec03f80-6bce-11e1-b2b8-6cf049ee52be Order Id: 4ec03f80-6bce-11e1-b2b9-6cf049ee52be Order Id: 4ec03f80-6bce-11e1-b2ba-6cf049ee52be Order Id: 4ec03f80-6bce-11e1-b2bb-6cf049ee52be Order Id: 4ec03f80-6bce-11e1-b2bc-6cf049ee52be Order Id: 4ec03f80-6bce-11e1-b2bd-6cf049ee52be Order Id: 4ec03f80-6bce-11e1-b2be-6cf049ee52be Order Id: 4ec03f80-6bce-11e1-b2bf-6cf049ee52be Hettie Feest (4ec50240-6bce-11e1-b300-6cf049ee52be) Points: 82 HasGoldStatus: False MemberSince: 4/10/2009 12:00:00 AM (Utc) CreditRating: Neutral AverageRating: 0 Orders: Order Id: 4ec50240-6bce-11e1-b301-6cf049ee52be Order Id: 4ec50240-6bce-11e1-b302-6cf049ee52be Order Id: 4ec50240-6bce-11e1-b303-6cf049ee52be Press <ENTER> to exit…
让我们看另一个简单的例子,我们将检索名字等于"Laverne"的客户
using System; using System.Data; using System.Linq; using System.Reflection; using HibernatingRhinos.Profiler.Appender.NHibernate; using NHibernate.Cfg; using NHibernate.Criterion; 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(); using(var session = sessionFactory.OpenSession()) using(var tx = session.BeginTransaction()) { var customers = session.CreateCriteria<Customer>() .Add(Restrictions.Eq("FirstName", "Laverne")) .List<Customer>(); foreach (var customer in customers) { Console.WriteLine(customer); } tx.Commit(); } Console.WriteLine("Press <ENTER> to exit..."); Console.ReadLine(); } 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; } } }
让我们再次运行该应用程序,您将看到以下输出。
Laverne Hegmann (4e97c816-6bce-11e1-b095-6cf049ee52be) Points: 74 HasGoldStatus: True MemberSince: 4/4/2009 12:00:00 AM (Utc) CreditRating: Neutral AverageRating: 0 Orders: Order Id: 4ea14d96-6bce-11e1-b095-6cf049ee52be Order Id: 4ea14d96-6bce-11e1-b096-6cf049ee52be Order Id: 4ea14d96-6bce-11e1-b097-6cf049ee52be Order Id: 4ea14d96-6bce-11e1-b098-6cf049ee52be Press <ENTER> to exit...
现在,标准 API 的主要缺点之一是属性名称中的这些不透明字符串。因此,如果将名字重构为其他名称,重构工具不一定会选择不透明字符串。
NHibernate - QueryOver 查询
在本章中,我们将介绍 QueryOver 查询。这是一种新的语法,更像 LINQ,使用方法链语法,如以下查询所示。
var customers = session.QueryOver<Customer>() .Where(x => x.FirstName == "Laverne");
它仍然是隐藏的条件,但现在我们的查询是强类型的。
正如我们在条件查询中看到的,名字只是一个不透明的字符串,现在我们实际上使用的是 x.FirstName,因此名字被重构和重命名,并在使用查询的链接样式条件查询中进行了更改。
我们仍然可以做很多类似的事情,但是您不能将查询理解语法与查询一起使用,您必须使用方法链语法,并且不能混合搭配链接和条件。
对于许多查询,查询 API 非常有用,并且提供了比直接使用条件更容易理解的对象语法。
让我们看一个简单的例子,我们将检索一个名字是 Laverne 的客户使用查询。
using System; using System.Data; using System.Linq; using System.Reflection; using HibernatingRhinos.Profiler.Appender.NHibernate; using NHibernate.Cfg; using NHibernate.Criterion; 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(); using(var session = sessionFactory.OpenSession()) using(var tx = session.BeginTransaction()) { var customers = session.QueryOver<Customer>() .Where(x => x.FirstName == "Laverne"); foreach (var customer in customers.List()) { Console.WriteLine(customer); } tx.Commit(); } Console.WriteLine("Press <ENTER> to exit..."); Console.ReadLine(); } 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; } } }
正如您所见,它本质上仍然是 Criteria,但语法更简洁。
当编译并执行上述代码时,您将看到以下输出。
Laverne Hegmann (4e97c816-6bce-11e1-b095-6cf049ee52be) Points: 74 HasGoldStatus: True MemberSince: 4/4/2009 12:00:00 AM (Utc) CreditRating: Neutral AverageRating: 0 Orders: Order Id: 4ea14d96-6bce-11e1-b095-6cf049ee52be Order Id: 4ea14d96-6bce-11e1-b096-6cf049ee52be Order Id: 4ea14d96-6bce-11e1-b097-6cf049ee52be Order Id: 4ea14d96-6bce-11e1-b098-6cf049ee52be Press <ENTER> to exit...
其中一个缺点是,假设我们想说 FirstName.StartsWith("A"),如下面的程序所示。
var customers = session.QueryOver<Customer>() .Where(x => x.FirstName.StartsWith("A")); foreach (var customer in customers.List()) { Console.WriteLine(customer); } tx.Commit();
现在让我们再次运行该应用程序,您将看到这不是 LINQ 提供程序,因为它不知道此 StartsWith 方法是什么,因此您将收到 RunTime 异常。

异常表示无法识别的方法调用。在这里我们做了显而易见的事情,但它不一定有效。
让我们尝试其他方法,例如 FirstName 等于"A%",如以下代码所示。
var customers = session.QueryOver<Customer>() .Where(x => x.FirstName == "A%"); foreach (var customer in customers.List()) { Console.WriteLine(customer); }
让我们再次运行它,您将看到我们不会得到任何结果,如下所示。
Press <ENTER> to exit...
要了解为什么我们没有得到任何结果,让我们看一下 NHibernate 分析器。

正如您所见,第一个名称等于 A%,但事实并非如此。A% 在 SQL 中与 like 运算符一起使用。现在我们需要在 WHERE 子句中创建一个限制,如以下程序所示。
var customers = session.QueryOver<Customer>() .Where(Restrictions.On<Customer>(c => c.FirstName).IsLike("A%")); foreach (var customer in customers.List()) { Console.WriteLine(customer); }
让我们再次运行您的应用程序,您将看到检索到的所有客户的名字均以 A 开头。
Alejandrin Will (4ea3aef6-6bce-11e1-b0b4-6cf049ee52be) Points: 24 HasGoldStatus: False MemberSince: 10/1/2011 12:00:00 AM (Utc) CreditRating: VeryVeryGood AverageRating: 0 Orders: Order Id: 4ea3aef6-6bce-11e1-b0b5-6cf049ee52be Austyn Nolan (4ea871b6-6bce-11e1-b110-6cf049ee52be) Points: 67 HasGoldStatus: True MemberSince: 12/29/2007 12:00:00 AM (Utc) CreditRating: Neutral AverageRating: 0 Orders: Order Id: 4ea871b6-6bce-11e1-b111-6cf049ee52be Antonia Murphy (4ea871b6-6bce-11e1-b121-6cf049ee52be) Points: 72 HasGoldStatus: True MemberSince: 6/15/2009 12:00:00 AM (Utc) CreditRating: Terrible AverageRating: 0 Orders: Order Id: 4ea871b6-6bce-11e1-b122-6cf049ee52be Order Id: 4ea871b6-6bce-11e1-b123-6cf049ee52be Order Id: 4ea871b6-6bce-11e1-b124-6cf049ee52be Order Id: 4ea871b6-6bce-11e1-b125-6cf049ee52be Order Id: 4ea871b6-6bce-11e1-b126-6cf049ee52be Order Id: 4ea871b6-6bce-11e1-b127-6cf049ee52be Order Id: 4ea871b6-6bce-11e1-b128-6cf049ee52be Order Id: 4ea871b6-6bce-11e1-b129-6cf049ee52be Order Id: 4ea871b6-6bce-11e1-b12a-6cf049ee52be
除了使用这种新的 QueryOver 语法外,它的工作方式与以前相同。许多开发人员发现 LINQ 语法更易于理解,而且通常能完成正确的任务。
如果 LINQ 无法处理,那么您将开始查看 HQL 或 Criteria,看看它们是否更合适。
它只是为您提供了一种不同的语法,因此 Criteria、创建条件和 QueryOver 为您提供了另一种查询机制,允许您使用 NHibernate 从数据库中提取数据。
NHibernate - 原生 SQL
在本章中,我们将介绍如何使用 NHibernate 中的原生 SQL 查询。如果您已经使用手写 SQL 多年,您可能会担心 ORM 会带走您习惯的一些表现力和灵活性。
NHibernate 强大的查询功能允许您执行几乎任何在 SQL 中可以执行的操作,在某些情况下甚至更多。
在极少数情况下,您无法让 NHibernate 自己的查询功能完全按照您的意愿执行。
NHibernate 允许您使用数据库的原生 SQL 方言检索对象。
让我们看一个 NHibernate 中原生 SQL 查询的简单示例。
using System; using System.Data; using System.Linq; using System.Reflection; using HibernatingRhinos.Profiler.Appender.NHibernate; using NHibernate.Cfg; using NHibernate.Criterion; using NHibernate.Dialect; using NHibernate.Driver; using NHibernate.Linq; using NHibernate; namespace NHibernateDemo { internal class Program { private static void Main() { var cfg = ConfigureNHibernate(); var sessionFactory = cfg.BuildSessionFactory(); using(var session = sessionFactory.OpenSession()) using(var tx = session.BeginTransaction()) { IQuery sqlQuery = session.CreateSQLQuery("SELECT * FROM CUSTOMER").AddEntity(typeof(Customer)); var customers = sqlQuery.List<Customer>(); foreach (var customer in customers) { Console.WriteLine(customer); } tx.Commit(); } Console.WriteLine("Press <ENTER> to exit..."); Console.ReadLine(); } 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; } } }
上述示例使用 CreateSQLQuery() 返回对象列表,您还会注意到,您希望查询返回的根实体类型指定为 Customer。
让我们运行您的应用程序,您将看到从数据库中检索到所有客户。
Emerson Prosacco (4ec2a0e0-6bce-11e1-b2cf-6cf049ee52be) Points: 17 HasGoldStatus: False MemberSince: 6/22/2007 12:00:00 AM (Utc) CreditRating: Excellent AverageRating: 0 Orders: Order Id: 4ec2a0e0-6bce-11e1-b2d0-6cf049ee52be Order Id: 4ec2a0e0-6bce-11e1-b2d1-6cf049ee52be Order Id: 4ec2a0e0-6bce-11e1-b2d2-6cf049ee52be Order Id: 4ec2a0e0-6bce-11e1-b2d3-6cf049ee52be Order Id: 4ec2a0e0-6bce-11e1-b2d4-6cf049ee52be Kaci Friesen (4ec2a0e0-6bce-11e1-b2d5-6cf049ee52be) Points: 30 HasGoldStatus: True MemberSince: 5/25/2007 12:00:00 AM (Utc) CreditRating: VeryVeryGood AverageRating: 0 Orders: Order Id: 4ec2a0e0-6bce-11e1-b2d6-6cf049ee52be Order Id: 4ec2a0e0-6bce-11e1-b2d7-6cf049ee52be Order Id: 4ec2a0e0-6bce-11e1-b2d8-6cf049ee52be Order Id: 4ec2a0e0-6bce-11e1-b2d9-6cf049ee52be Order Id: 4ec2a0e0-6bce-11e1-b2da-6cf049ee52be Order Id: 4ec2a0e0-6bce-11e1-b2db-6cf049ee52be Eveline Waters (4ec2a0e0-6bce-11e1-b2dc-6cf049ee52be) Points: 58 HasGoldStatus: False MemberSince: 10/29/2009 12:00:00 AM (Utc) CreditRating: Good AverageRating: 0 Orders: Order Id: 4ec2a0e0-6bce-11e1-b2dd-6cf049ee52be Order Id: 4ec2a0e0-6bce-11e1-b2de-6cf049ee52be Order Id: 4ec2a0e0-6bce-11e1-b2df-6cf049ee52be Order Id: 4ec2a0e0-6bce-11e1-b2e0-6cf049ee52be Order Id: 4ec2a0e0-6bce-11e1-b2e2-6cf049ee52be Molly Kuhn (4ec2a0e0-6bce-11e1-b2e3-6cf049ee52be) Points: 73 HasGoldStatus: False MemberSince: 12/16/2007 12:00:00 AM (Utc) CreditRating: VeryGood AverageRating: 0 Orders: Order Id: 4ec2a0e0-6bce-11e1-b2e4-6cf049ee52be Order Id: 4ec2a0e0-6bce-11e1-b2e5-6cf049ee52be Order Id: 4ec2a0e0-6bce-11e1-b2e6-6cf049ee52be Order Id: 4ec2a0e0-6bce-11e1-b2e7-6cf049ee52be Order Id: 4ec2a0e0-6bce-11e1-b2e8-6cf049ee52be Order Id: 4ec2a0e0-6bce-11e1-b2e9-6cf049ee52be Order Id: 4ec2a0e0-6bce-11e1-b2ea-6cf049ee52be Order Id: 4ec2a0e0-6bce-11e1-b2eb-6cf049ee52be Order Id: 4ec2a0e0-6bce-11e1-b2ec-6cf049ee52be
以下是另一种编写本机 SQL 查询的方法。
IList<Customer> customers = session.CreateSQLQuery("SELECT * FROM CUSTOMER") .AddScalar("Id", NHibernateUtil.Guid) .AddScalar("FirstName", NHibernateUtil.String) .AddScalar("LastName", NHibernateUtil.String) .List<Customer>();
如您所见,上述查询指定了 SQL 查询字符串以及要返回的列和类型。
这将返回一个 IList,其中包含 Customer 表中每列的标量值。
即使查询使用 * 并可能返回超过三个列出的列,也只会返回这三列。
让我们看另一个简单的例子。
IList<Customer> customers = session.CreateSQLQuery("SELECT * FROM CUSTOMER WHERE FirstName = 'Laverne'") .AddEntity(typeof(Customer)) .List<Customer>(); foreach (var customer in customers) { Console.WriteLine(customer); }
让我们再次运行您的应用程序,您将看到以下输出。
Laverne Hegmann (4e97c816-6bce-11e1-b095-6cf049ee52be) Points: 74 HasGoldStatus: True MemberSince: 4/4/2009 12:00:00 AM (Utc) CreditRating: Neutral AverageRating: 0 Orders: Order Id: 4ea14d96-6bce-11e1-b095-6cf049ee52be Order Id: 4ea14d96-6bce-11e1-b096-6cf049ee52be Order Id: 4ea14d96-6bce-11e1-b097-6cf049ee52be Order Id: 4ea14d96-6bce-11e1-b098-6cf049ee52be Press <ENTER> to exit...
同样,您可以指定任何类型的 SQL 查询来从数据库中检索数据。
NHibernate - Fluent Hibernate
在本章中,我们将介绍 Fluent NHibernate。Fluent NHibernate 是另一种映射方式,或者您可以说它是 NHibernate 标准 XML 映射文件的替代方案。无需编写 XML (.hbm.xml 文件) 文档。借助 Fluent NHibernate,您可以在强类型 C# 代码中编写映射。
在 Fluent NHibernate 中,映射与应用程序的其余部分一起编译。
您可以像应用程序代码一样轻松更改映射,并且编译器不会对任何拼写错误进行处理。
它有一个常规配置系统,您可以在其中指定模式以覆盖命名约定和许多其他内容。
您还可以设置一次事物的命名方式,然后 Fluent NHibernate 会完成其余的工作。
让我们通过创建一个新的控制台项目来查看一个简单的示例。在本章中,我们将使用一个简单的数据库,其中有一个简单的客户表,如下图所示。

安装 Fluent NHibernate
启动 Fluent NHibernate 的第一步是安装 Fluent NHibernate 包。因此,打开 NuGet 包管理器控制台 并输入以下命令。
PM> install-package FluentNHibernate
一旦成功安装,您将看到以下消息。

让我们添加一个简单的 Customer 模型类,以下程序显示了 Customer 类的实现。
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace FluentNHibernateDemo { class Customer { public virtual int Id { get; set; } public virtual string FirstName { get; set; } public virtual string LastName { get; set; } } }
现在我们需要使用流畅的 NHibernate 创建映射,因此在项目中添加一个类 CustomerMap。以下是 CustomerMap 类的实现。
using FluentNHibernate.Mapping; using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace FluentNHibernateDemo { class CustomerMap : ClassMap<Customer> { public CustomerMap() { Id(x => x.Id); Map(x => x.FirstName); Map(x => x.LastName); Table("Customer"); } } }
让我们添加另一个类 NHibernateHelper,我们将在其中设置不同的配置设置。
using FluentNHibernate.Cfg; using FluentNHibernate.Cfg.Db; using NHibernate; using NHibernate.Tool.hbm2ddl; namespace FluentNHibernateDemo { public class NHibernateHelper { private static ISessionFactory _sessionFactory; private static ISessionFactory SessionFactory { get { if (_sessionFactory == null) InitializeSessionFactory(); return _sessionFactory; } } private static void InitializeSessionFactory() { _sessionFactory = Fluently.Configure() String Data Source = asia13797\sqlexpress; String Initial Catalog = NHibernateDemoDB; String Integrated Security = True; String Connect Timeout = 15; String Encrypt = False; String TrustServerCertificate = False; String ApplicationIntent = ReadWrite; String MultiSubnetFailover = False; .Database(MsSqlConfiguration.MsSql2008 .ConnectionString( @"Data Source + Initial Catalog + Integrated Security + Connect Timeout + Encrypt + TrustServerCertificate + ApplicationIntent + MultiSubnetFailover") .ShowSql() ) .Mappings(m => m.FluentMappings .AddFromAssemblyOf<Program>()) .ExposeConfiguration(cfg => new SchemaExport(cfg) .Create(true, true)) .BuildSessionFactory(); } public static ISession OpenSession() { return SessionFactory.OpenSession(); } } }
现在让我们转到 Program.cs 文件,我们将在其中启动一个会话,然后创建一个新客户并将该客户保存到数据库,如下所示。
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace FluentNHibernateDemo { class Program { static void Main(string[] args) { using (var session = NHibernateHelper.OpenSession()) { using (var transaction = session.BeginTransaction()) { var customer = new Customer { FirstName = "Allan", LastName = "Bomer" }; session.Save(customer); transaction.Commit(); Console.WriteLine("Customer Created: " + customer.FirstName + " " + customer.LastName); } Console.ReadKey(); } } } }
让我们运行您的应用程序,您将看到以下输出。
if exists (select * from dbo.sysobjects where id = object_id(N'Customer') and OBJECTPROPERTY(id, N'IsUserTable') = 1) drop table Customer create table Customer ( Id INT IDENTITY NOT NULL, FirstName NVARCHAR(255) null, LastName NVARCHAR(255) null, primary key (Id) ) NHibernate: INSERT INTO Customer (FirstName, LastName) VALUES (@p0, @p1); select SCOPE_IDENTITY();@p0 = 'Allan' [Type: String (4000)], @p1 = 'Bomer' [Type: String (4000)] Customer Created: Allan Bomer
如您所见,新客户已创建。要查看客户记录,让我们转到数据库并查看"查看数据",您将看到已添加 1 个客户。
