- NHibernate 教程
- NHibernate - 首页
- NHibernate - 概述
- NHibernate - 架构
- NHibernate - ORM
- NHibernate - 环境搭建
- NHibernate - 入门
- NHibernate - 基本 ORM
- NHibernate - 基本 CRUD 操作
- NHibernate - Profiler
- 为映射文件添加 IntelliSense
- NHibernate - 数据类型映射
- NHibernate - 配置
- NHibernate - 覆盖配置
- NHibernate - 批处理大小
- NHibernate - 缓存
- NHibernate - 映射组件
- NHibernate - 关系
- NHibernate - 集合映射
- NHibernate - 级联操作
- NHibernate - 延迟加载
- NHibernate - 反向关系
- NHibernate - Load/Get
- NHibernate - LINQ
- NHibernate - 查询语言
- NHibernate - Criteria 查询
- NHibernate - QueryOver 查询
- NHibernate - 原生 SQL
- NHibernate - Fluent Hibernate
- NHibernate 有用资源
- NHibernate 快速指南
- NHibernate - 有用资源
- NHibernate - 讨论
NHibernate 快速指南
NHibernate - 概述
在本节中,我们将讨论 NHibernate 是什么,它可以在哪些平台上实现,它的优势是什么以及其他相关方面。
什么是 NHibernate?
NHibernate 是一个成熟的、开源的 .NET 框架对象关系映射器。它处于积极开发阶段,功能齐全,并被成千上万个成功的项目使用。它构建在 **ADO.NET** 之上,当前版本为 **NHibernate 4.0.4**。
NHibernate 是一个开源的 .NET 对象关系映射器,并在 **GNU Lesser General Public License** 下分发。
它基于 Hibernate,Hibernate 是一个流行的 Java 对象关系映射器,它拥有一个非常成熟和活跃的代码库。
它提供了一个框架,用于将面向对象的领域模型映射到传统的关联数据库。
NHibernate 由 **Tom Barrett** 发起,该项目自 2003 年 2 月以来一直存在,当时是他们的第一次提交。
这是一个大型项目,提供了许多功能。
有一个 **NuGet 包** 可用,这使得将其添加到项目中变得非常容易。
为什么要使用 NHibernate?
现在问题是,我们为什么要需要 **对象关系映射器?** 这是因为对象世界和关系世界之间存在脱节。
在对象世界中,一切都是围绕 **对象** 构建的;我们称之为对象,那些包含我们数据的事物。
关系世界是基于集合的,我们处理的是表和行,这与对象世界不同。
在对象世界中,我们有 **单向关联**。如果一个客户有一个指向订单的指针,它并不一定意味着订单有一个指向客户的指针,它可能存在也可能不存在。
在关系世界中,所有关联都是 **双向** 的,可以通过外键实现。
所有关联本质上都是双向的,因此当我们处理对象关系映射时,我们也需要处理这种脱节。
在对象世界中,我们使用的是单向指针,而在关系世界中,我们使用的是本质上是双向的外键。
对象世界具有继承的概念,其中车辆可以有多个不同的子类,因此汽车是一种车辆,船是一种车辆,跑车是一种汽车,这些类型的继承关系。
关系世界没有这种继承的概念。
映射
那么我们如何映射所有这些 **不相关的关系?** 这个映射的概念来自对象关系映射器。主要有三个方面需要理解,如下面的图所示。
在您的应用程序中,您将需要类定义,这通常是 C# 代码,以及表示我们的类的 .NET 代码,例如 Employee 类、Customer 类、Order 类等。
在底部,您可以看到一个数据库模式,它是在关系数据库中定义 **数据定义语言**,用于指定客户表的外观,员工表的外观。
在这两者之间,我们有映射元数据,它告诉对象关系映射器如何从 C# 中的对象世界转换为数据库世界中的行和列以及外键关系。
此映射元数据可以通过多种不同的方式表示,我们将查看 NHibernate 应用程序中最典型的一些不同方式。
它由 **HBM(Hibernate 映射)** 文件表示,它们是 XML 文件。
支持的数据库
NHibernate 支持各种不同的数据库。任何现有的关系数据库都可以被 NHibernate 访问。
SQL Server 是主要支持的数据库,大多数开发人员在开发过程中都在使用它,它可能是最常见的数据库。
它也 **与 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,用于唯一标识每个员工。
一列用于员工姓名,另一列用于其入职日期,最后是一列用于员工年龄。
如果我们想编写一些代码来将新员工存储到表中,这并不容易。
在上图中,您还可以看到我们有一个 Employee 对象,它具有 Id、name、joining date 和 age 的字段。
如果没有 ORM,我们必须将此对象转换为几个不同的 SQL 语句,这些语句将员工数据插入到 Employee 表中。
因此,编写代码来创建 SQL 以执行上述方案并不难,但有点乏味且很容易出错。
使用像 NHibernate 这样的 ORM,我们可以声明某些类应该如何映射到关系表,并让 ORM 或 NHibernate 处理创建 SQL 的繁琐工作,以便在我们的 Employee 表中插入、更新、删除和查询数据。
这使我们能够将代码集中在使用对象上,并使这些对象自动转换为关系表。
因此,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 包管理器”→“程序包管理器控制台”,这将打开“程序包管理器控制台”窗口。
指定上面程序包管理器控制台窗口中显示的命令并按 Enter 键,它将下载所有 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 对象资源管理器中的数据库,然后选择“属性”。
它将打开“属性”窗口,现在向下滚动,您将在“属性”窗口中看到“连接字符串”字段。
复制连接字符串并在代码中指定。以下是 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 可用于构建会话,会话大致类似于数据库连接。因此,适当的方法是在 using 块中使用它。我可以说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,因此我们的生成器将是 native 类型。
native 类型生成器意味着在像 SQL Server 这样的数据库中,它将使用 identity 列,即 identity 类型。
接下来我们要做的事情是给出属性的名称。因此,为 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 对象资源管理器并刷新您的数据库。您将看到上述两个学生现在已添加到 NHibernateDemoDB 数据库的 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} \t{1} \t{2}", student.ID,student.FirstMidName, student.LastName); } tx.Commit(); } Console.ReadLine(); }
因此,如果您想要记录列表,我们可以简单地说 Student 类型的列表。
现在使用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} \t{1} \t{2}", student.ID, student.FirstMidName, student.LastName); } var stdnt = session.Get<Student>(1); Console.WriteLine("Retrieved by ID"); Console.WriteLine("{0} \t{1} \t{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} \t{1} \t{2}", student.ID, student.FirstMidName, student.LastName); } var stdnt = session.Get<Student>(1); Console.WriteLine("Retrieved by ID"); Console.WriteLine("{0} \t{1} \t{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("\nFetch the complete list again\n"); foreach (var student in students) { Console.WriteLine("{0} \t{1} \t{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} \t{1} \t{2}", student.ID, student.FirstMidName, student.LastName); } var stdnt = session.Get<Student>(1); Console.WriteLine("Retrieved by ID"); Console.WriteLine("{0} \t{1} \t{2}", stdnt.ID, stdnt.FirstMidName, stdnt.LastName); Console.WriteLine("Delete the record which has ID = {0}", stdnt.ID); session.Delete(stdnt); Console.WriteLine("\nFetch the complete list again\n"); foreach (var student in students) { Console.WriteLine("{0} \t{1} \t{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("\nFetch the complete list again\n"); var students = session.CreateCriteria<Student>().List<Student>(); foreach (var student in students) { Console.WriteLine("{0} \t{1} \t{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("\nFetch the complete list again\n"); foreach (var student in students) { Console.WriteLine("{0} \t{1} \t{2}", student.ID, student.FirstMidName, student.LastName); } tx.Commit(); } Console.ReadLine(); } } } }
现在,当你运行应用程序时,它会将数据发送到 NHibernate Profiler 应用程序。
你可以在这里看到,我们有一个很好的显示,显示我们已经启动了事务,SQL 以一种很好的格式对数据库做了什么操作。
所以这对于确定 NHibernate 应用程序内部到底发生了什么非常有用。一旦应用程序达到一定的复杂程度,它就会变得非常有用,这时你需要一些更像 SQL Profiler 的东西,但同时具备 NHibernate 的知识。
为映射文件添加 IntelliSense
在本节中,我们将向我们的 NHibernate 映射文件(*.hbm.xml 文件)添加IntelliSense。正如你所观察到的,在映射域 Student 类时,我们目前没有可用的 IntelliSense。拥有XML 架构非常有用。因此,在本节中,你将了解如何在 Visual Studio 中为这些 NHibernate XML 文件添加 IntelliSense。
打开映射文件,你将看到 XML 菜单选项出现在主菜单中。
选择 XML → 架构… 菜单选项,它将显示 XML 架构对话框。
选择位于对话框右上角的添加… 按钮,它将打开文件对话框。现在转到包文件夹,它位于项目解决方案文件夹中,你将看到项目中包含的不同包。
现在,双击NHibernate.4.*** 文件夹,你将看到两个架构 (*.xsd) 文件或 XML 架构定义文件,它们定义了 NHibernate 配置和映射。
选择这两个架构文件,然后单击打开按钮。
你可以看到 NHibernate 架构已添加到 XML 架构对话框中。单击确定按钮。现在,让我们开始一个新的属性标签,你将看到我们在这里拥有完整的 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—一个字符 |
AnsiChar | System.Char | DbType.AnsiStringFixedLength—一个字符 |
Boolean | System.Boolean | DbType.Boolean |
Guid | System.Guid | DbType.Guid |
PersistentEnum | System.Enum(枚举) | 底层值的 DbType |
TrueFalse | System.Boolean | DbType.AnsiStringFixedLength—'T' 或 'F' |
YesNo | System.Boolean | DbType.AnsiStringFixedLength—'Y' 或 'N' |
DateTime | DateTime | DbType.DateTime—忽略毫秒 |
Ticks | System.DateTime | DbType.Int64 |
TimeSpan | System.TimeSpan | DbType.Int64 |
Timestamp | System.DateTime | DbType.DateTime—尽可能具体地支持数据库 |
Binary | System.Byte[] | DbType.Binary |
BinaryBlob | System.Byte[] | DbType.Binary |
StringClob | System.String | DbType.String |
Serializable | 任何用 SerializableAttribute 标记的 System.Object | DbType.Binary |
CultureInfo | System.Globalization.CultureInfo | DbType.String—文化五字符 |
Type | System.Type | DbType.String 保存程序集限定名 |
上表详细说明了下列要点。
从简单的数字类型到字符串,可以使用varchar、char等各种方式进行映射,以及字符串 blob 和数据库支持的所有类型的各种类型。
它还能够映射布尔值,既可以映射到使用零和一的字段,也可以映射到包含 true、false 或 T 和 F 的字符字段。
有各种方法可以定义它如何映射到后端,数据库中的布尔值。
我们可以处理DateTime的映射,包括和不包括时区偏移、夏令时等。
我们还可以映射枚举;我们可以将它们映射到字符串或其底层数值。
让我们来看一个简单的示例,其中我们在数据库和 Student 类中都具有相同的属性名称。
现在让我们将 Student 类中的 FirstMidName 更改为 FirstName,我们不会更改 FirstMidName 列,但我们将看到如何告诉 NHibernate 执行此转换。以下是更新后的 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; } } }
这是 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 如何执行此转换,请将 name 设置为FirstName,将 column 设置为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("\nFetch the complete list again\n"); foreach (var student in students) { Console.WriteLine("{0} \t{1} \t{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
正如你所看到的,它已将不同的属性名称映射到数据库中的列名称。
让我们看另一个示例,在这个示例中,我们将在 Student 类中添加另一个enum类型的属性。以下是 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 } }
正如你所看到的,枚举可以具有各种不同的值,例如 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 表,然后创建一个新表。
单击上面所示的执行图标。查询成功执行后,你将看到一条消息。
展开数据库和表下拉菜单,然后右键单击 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("\nFetch the complete list again\n"); foreach (var student in students) { Console.WriteLine("{0} \t{1} \t{2} \t{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 表来查看数据库。
选择查看数据,你将在学生表中看到两条记录,如下面的屏幕截图所示。
你可以看到添加了两条记录,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 中引入的流畅配置来配置数据库。我们使用什么连接字符串,连接到什么数据库以及要使用的方言。我们还将我们的映射程序集直接添加到此处。
基于 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("\nFetch the complete list again\n"); foreach (var student in students) { Console.WriteLine("{0} \t{1} \t{2} \t{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 文件,也不必只使用基于代码的配置或 Fluent 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("\nFetch the complete list again\n"); foreach (var student in students) { Console.WriteLine("{0} \t{1} \t{2} \t{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("\nFetch the complete list again\n"); foreach (var student in students) { Console.WriteLine("{0} \t{1} \t{2} \t{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**。这样就可以正常工作了。现在让我们使用以下代码从 native 更改为 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。
执行此查询,然后转到 **设计器视图**,您将看到现在 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("\nFetch the complete list again\n"); foreach (var student in students) { Console.WriteLine("{0} \t{1} \t{2} \t{3}", student.ID, student.FirstName,student.LastName, student.AcademicStanding); } } Console.ReadLine(); } } } }
现在再次运行应用程序并查看 NHibernate 性能分析器。现在,NHibernate 性能分析器而不是进行 26 次往返操作,只会进行 4 次。
它已将 10 行插入表中,然后是另外 10 行,然后是剩余的 5 行。提交后,它还插入了一行以检索所有记录。
所以它将其划分为 10 组,尽可能地进行。
因此,如果您进行大量插入,这可以显着提高应用程序中的插入性能,因为您可以将其批处理。
这是因为 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 性能分析器中查看结果。
您会惊讶地发现 NHibernate 只触发了一个查询。这就是 NHibernate 如何使用一级缓存。当第一个查询执行时,NHibernate 将 ID = 1 的 Student 缓存在其一级缓存中。
因此,当执行第二个查询时,NHibernate 首先查找 ID = 1 的一级缓存 Student 实体,如果找到该实体,则 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>
此组件是地址,它具有这些不同的属性。有了这些信息,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("\nFetch the complete list again\n"); foreach (var student in students) { Console.WriteLine("{0} \t{1} \t{2} \t{3} \t{4} \t{5} \t{6} \t{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 是一个类,它不是一个实体。
它是一个值类型对象,并且没有自己的主键。
它与包含它的 Student 保存在同一个表中。
这就是我们在这里使用组件的原因。
这允许很大的灵活性来更改我们的类层,我们的类是如何定义的以及我们的数据库是如何布局的。
NHibernate - 关系
在本章中,我们将研究 NHibernate 中的关系。让我们把注意力转向如何理解 NHibernate 中的关系。最简单的方法是从数据库的角度考虑关系。
我们首先创建一个新的应用程序,在该应用程序中,我们将创建客户和订单实体之间的一些关系。
我们将要研究的第一个关系是一个经典的集合关系。
我们有一个客户,拥有一个订单集合。
这是一个一对多关系,它在数据库中由 2 个表表示,订单表上有一个客户 ID,并且我们有一个指向客户的反向外键关系。
首先,我们需要创建一个数据库和两个表 Customer 和 Order。您可以通过在 SQL Server 资源管理器中指定以下查询来创建它。
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
它将在数据库中创建两个表。下图显示了 Customer 表。
下图显示了 Order 表,您可以在其中看到指向客户的反向外键关系。
我们需要在 **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 Manager Console 窗口中运行以下命令。
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})\r\n\tPoints: {3}\r\n\tHasGoldStatus: {4}\r\n\tMemberSince: {5} ({7})\r\n\tCreditRating: {6}\r\n\tAverageRating: {8}\r\n", Id, FirstName, LastName, Points, HasGoldStatus, MemberSince, CreditRating, MemberSince.Kind, AverageRating); result.AppendLine("\tOrders:"); foreach(var order in Orders) { result.AppendLine("\t\t" + order); } return result.ToString(); } } public class Location { public virtual string Street { get; set; } public virtual string City { get; set; } public virtual string Province { get; set; } public virtual string Country { get; set; } } public enum CustomerCreditRating { Excellent, VeryVeryGood, VeryGood, Good, Neutral, Poor, Terrible } }
以下是映射文件 **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 识别或属于客户。然后我必须注意这是一个一对多关系,它与订单类相关。
我们还需要稍微修改一下 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 中,我们可以使用不同类型的集合,例如:
- 列表
- 集合
- 包
现在,从 .NET 的角度来看,我们通常处理列表或非常简单的数据结构,例如列表、字典。.NET 没有各种各样的集合类型。那么为什么 NHibernate 需要所有这些不同的类型呢?这实际上回到了数据库。
列表
列表是有序的元素集合,这些元素不一定唯一。
我们可以使用IList <T>进行映射。
因此,虽然我们可能通常有一个地址列表,并且从应用程序的角度来看,我们知道这些元素是唯一的,但是列表中没有任何内容可以阻止我们在该列表中插入重复元素。
集合
集合是唯一元素的无序集合。如果您尝试将 2 个重复元素插入集合中,它将抛出异常。
NHibernate 中对此没有具体说明。
这只是一个方便的方法,可以使用泛型集合实现。如果您使用的是 .NET 4,则可以使用新的HashSet <T>来表示这些集合,但在大多数 NHibernate 应用程序中,我们将其表示为 ISet。
它是无序的,如果您从数据库中提取地址列表或订单列表,除非您放入特定的 Order by 子句,否则您不知道它们的顺序。
因此,通常,您从数据库中提取的数据是集合。
它们是唯一元素的无序集合。
包
我们在数据库世界中会看到的另一个常见集合是包,它就像集合一样,除了它可以包含重复元素。
在 .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>
现在,如果您运行此应用程序,您将收到异常,因为如果我们查看 customer 类,您会注意到订单在 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})\r\n\tPoints: {3}\r\n\tHasGoldStatus: {4}\r\n\tMemberSince: {5} ({7})\r\n\tCreditRating: {6}\r\n\tAverageRating: {8}\r\n", Id, FirstName, LastName, Points, HasGoldStatus, MemberSince, CreditRating, MemberSince.Kind, AverageRating); result.AppendLine("\tOrders:"); foreach(var order in Orders) { result.AppendLine("\t\t" + order); } return result.ToString(); } }
运行应用程序后,您将看到相同的行为。但是,现在我们可以使订单在同一个集合中多次出现。
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 - 级联操作
在本章中,我们将介绍如何使用 Cascade 功能。如果您有一组或多组项目或两个类(例如我们的客户和订单)之间的关系,并且存在外键关系。如果我们删除客户,默认情况下,NHibernate 不会对子对象执行任何操作,因此属于该客户的对象可能会变成孤立订单。
我们也可能违反外键约束,因此我们可以使用级联的概念。
默认情况下,NHibernate 不会将操作级联到子对象。
这样做的原因是,您可能存在这样的关系:客户拥有默认送货地址,并且该送货地址与许多不同的客户共享。
因此,您不希望一定级联该关系,因为其他客户仍在引用它。
因此,级联的整个概念是告诉 NHibernate 如何处理其子实体。
级联有不同的选项,如下所示:
none - 这是默认值,表示不级联。
all - 将级联保存、更新和删除操作。
save-update - 它将级联保存和更新操作。
delete - 它将级联删除操作。
all-delete-orphan - 这是一个特殊的选项,非常常用,与 All 相同,除了它会查找 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>
现在,订单完全属于客户。因此,如果客户从数据库中删除,我们的应用程序将希望删除所有这些订单,包括可能已成为孤立订单的任何订单。
它最终会执行删除操作。通过此操作,它会说删除 order 表中 customer 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>
因此,如果我们创建一个新订单,并且有一个新的客户附加到它,并且我们说保存该订单,我们可能希望级联它。但是,我们可能不希望做的一件事是,如果删除订单,则删除相应的客户。
因此,在这里,我们希望执行保存更新操作,因此使用 save-update,它将级联对该客户的任何保存或更新操作。因此,如果我们获得一个新客户或如果我们正在更改客户,它将级联该操作。如果它是删除操作,它不会从数据库中删除该客户。
因此,再次运行我们的应用程序,一切仍然按预期工作。
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 设置为 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 在客户表和订单表之间执行左外部连接,因此,它能够一次加载所有这些信息。
我们节省了 1 次对数据库的往返。
缺点是客户信息将在两行上重复,这是 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,它也让很多开发人员感到困惑。所以让我们谈谈这个选项。要理解这一点,您确实需要考虑关系模型。假设您使用单个外键进行双向关联。
从关系型数据库的角度来看,你有一个外键,它同时表示客户到订单和订单到客户的关系。
从面向对象模型的角度来看,你使用这些引用实现了单向关联。
没有任何规定说两个单向关联在数据库中就代表同一个双向关联。
这里的问题是,NHibernate 没有足够的信息来知道 **customer.orders** 和 **order.customer** 在数据库中代表的是同一种关系。
我们需要提供 **inverse equals true** 作为提示,这是因为这些单向关联使用了相同的数据。
如果我们尝试保存这些有两个引用的关系,NHibernate 会尝试更新该引用两次。
它实际上会进行额外的数据库往返操作,并且会对该外键进行两次更新。
inverse equals true 告诉 NHibernate 要忽略关系的哪一边。
当将其应用于集合端时,NHibernate 将始终从另一端,即从子对象端更新外键。
这样,我们只需要对该外键进行一次更新,并且不会对该数据进行额外的更新。
这使我们能够防止对该外键进行重复更新,也有助于我们防止外键违规。
让我们看看 **customer.cs** 文件,你会看到 **AddOrder** 方法,这里的想法是,我们现在有了从订单返回到客户的反向指针,并且需要设置它。因此,当一个订单被添加到一个客户时,该客户的反向指针会被设置,否则它将为 null,所以我们需要这样做才能在对象图中正确地将它们连接在一起。
using System; using System.Text; using Iesi.Collections.Generic; namespace NHibernateDemo { public class Customer { public Customer() { MemberSince = DateTime.UtcNow; Orders = new HashedSet<Order>(); } public virtual Guid Id { get; set; } public virtual string FirstName { get; set; } public virtual string LastName { get; set; } public virtual double AverageRating { get; set; } public virtual int Points { get; set; } public virtual bool HasGoldStatus { get; set; } public virtual DateTime MemberSince { get; set; } public virtual CustomerCreditRating CreditRating { get; set; } public virtual Location Address { get; set; } public virtual ISet<Order> Orders { get; set; } public virtual void AddOrder(Order order) { Orders.Add(order); order.Customer = this; } public override string ToString() { var result = new StringBuilder(); result.AppendFormat("{1} {2} ({0})\r\n\tPoints: {3}\r\n\tHasGoldStatus: {4}\r\n\tMemberSince: {5} ({7})\r\n\tCreditRating: {6}\r\n\tAverageRating: {8}\r\n", Id, FirstName, LastName, Points, HasGoldStatus, MemberSince, CreditRating, MemberSince.Kind, AverageRating); result.AppendLine("\tOrders:"); foreach(var order in Orders) { result.AppendLine("\t\t" + order); } return result.ToString(); } } public class Location { public virtual string Street { get; set; } public virtual string City { get; set; } public virtual string Province { get; set; } public virtual string Country { get; set; } } public enum CustomerCreditRating { Excellent, VeryVeryGood, VeryGood, Good, Neutral, Poor, Terrible } }
以下是Program.cs文件实现。
using System; using System.Data; using System.Linq; using System.Reflection; using HibernatingRhinos.Profiler.Appender.NHibernate; using NHibernate.Cfg; using NHibernate.Dialect; using NHibernate.Driver; using NHibernate.Linq; namespace NHibernateDemo { internal class Program { private static void Main() { var cfg = ConfigureNHibernate(); var sessionFactory = cfg.BuildSessionFactory(); Guid id; using(var session = sessionFactory.OpenSession()) using(var tx = session.BeginTransaction()) { var newCustomer = CreateCustomer(); Console.WriteLine("New Customer:"); Console.WriteLine(newCustomer); session.Save(newCustomer); id = newCustomer.Id; tx.Commit(); } using(var session = sessionFactory.OpenSession()) using(var tx = session.BeginTransaction()) { var query = from customer in session.Query<Customer>() where customer.Id == id select customer; var reloaded = query.Fetch(x => x.Orders).ToList().First(); Console.WriteLine("Reloaded:"); Console.WriteLine(reloaded); tx.Commit(); } Console.WriteLine("Press <ENTER> to exit..."); Console.ReadLine(); } private static Customer CreateCustomer() { var customer = new Customer { FirstName = "John", LastName = "Doe", Points = 100, HasGoldStatus = true, MemberSince = new DateTime(2012, 1, 1), CreditRating = CustomerCreditRating.Good, AverageRating = 42.42424242, Address = CreateLocation() }; var order1 = new Order { Ordered = DateTime.Now }; customer.AddOrder(order1); var order2 = new Order { Ordered = DateTime.Now.AddDays(-1), Shipped = DateTime.Now, ShipTo = CreateLocation() }; customer.AddOrder(order2); return customer; } private static Location CreateLocation() { return new Location { Street = "123 Somewhere Avenue", City = "Nowhere", Province = "Alberta", Country = "Canada" }; } private static Configuration ConfigureNHibernate() { NHibernateProfiler.Initialize(); var cfg = new Configuration(); cfg.DataBaseIntegration(x => { x.ConnectionStringName = "default"; x.Driver<SqlClientDriver>(); x.Dialect<MsSql2008Dialect>(); x.IsolationLevel = IsolationLevel.RepeatableRead; x.Timeout = 10; x.BatchSize = 10; }); cfg.SessionFactory().GenerateStatistics(); cfg.AddAssembly(Assembly.GetExecutingAssembly()); return cfg; } } }
它会将数据保存到数据库中,然后重新加载它。现在让我们运行你的应用程序并打开 NHibernate Profiler,看看它是如何实际保存数据的。
你会注意到我们有三组语句。第一个语句会插入客户,该客户的 ID 是 Guid,已高亮显示。第二个语句是插入 orders 表。
你会注意到相同的 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 Profiler。
如果我们看看它们是如何插入的,我们会得到在客户中插入,以及在订单中插入,但我们没有重复更新外键,因为它在订单保存时被更新了。
现在,请注意,如果你只有一个单向关联,并且是集合正在维护这种关系,那么如果你将 inverse 设置为 true,则该外键将永远不会被设置,并且这些项的数据库中的外键将永远不会被设置。
如果你查看 **Order.hbm.xml** 文件中的多对一关系,并查找 inverse,它实际上没有 inverse 属性。
它始终从子项设置,但如果你有多对多集合,则可以从任一侧设置。
NHibernate - Load/Get
在本章中,我们将介绍 Load 和 Get 功能的工作原理以及如何使用它们。这是 **ISession** 提供的两个非常相似的 API,用于通过主键加载对象。
**Get** - 它将返回对象或 null。
**Load** - 它将返回对象或抛出 **ObjectNotFoundException**。
现在,为什么我们有这两个不同的 API 呢?
Load
这是因为 Load 可以更有效地优化数据库往返次数。
Load 实际上返回一个代理对象,并且不需要在你发出 Load 调用时立即访问数据库。
当你访问该代理时,如果对象碰巧不在数据库中,它可以在那时抛出 ObjectNotFoundException。
Get
相反,对于 Get,由于 CLR(公共语言运行时)和 NHibernate 的限制,它必须立即转到数据库,检查对象是否存在,如果不存在则返回 null。
它没有延迟获取数据库往返操作到以后时间的选项,因为它无法返回代理对象,并且当用户实际访问它时,它将该代理对象替换为 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 为 null,并且两者类型均为 **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 窗口,你会看到两个对象的类型都是 customer proxy。你还会在控制台窗口中看到 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; } } }
让我们再次运行此应用程序,你将看到所有 FirstName 以字母 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'");
所以在这里你可以看到它们从 customer 中选择 C,它看起来很像 SQL。就 NHibernate 而言,这是一个不透明的字符串,因此你不知道它是否是有效的 HQL,直到运行时,这也是它的缺点之一。
LINQ 提供程序的优势之一是你可以获得编译时支持。
但是 HQL 是最灵活的查询机制之一,经常被使用。据说,如果没有其他方法可以做到,那么就有方法可以在 HQL 中做到。
让我们来看一个简单的示例,我们将使用 HQL 而不是 LINQ 查询来重新创建我们的 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; } } }
让我们再次运行你的应用程序,你将看到所有 FirstName 以字母 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 查询
在本章中,我们将介绍 Criteria 查询机制。**NHibernate 基于 Criteria 的查询 API** 允许你通过在运行时操作 Criteria 对象来构建查询。
这种方法允许你动态指定约束,而无需直接进行字符串操作,但它并没有失去 HQL 的太多灵活性和功能。
另一方面,用 Criteria 表达的查询通常不如用 HQL 表达的查询易读。
经典的 Criteria 语法是一个基于对象的查询 API,如下面的程序所示。
var customers = session.CreateCriteria<Customer>().Add(Restrictions.Like("FirstName", "H%"));
你可以看到我们正在对 customer 执行 session create criteria,现在我们正在向该查询添加 restriction 对象。
这对于用户可以选择某些选项但不能选择其他选项的查询页面很有用。
构建查询作为树状查询结构,而不是在 HQL 或 LINQ 中使用 AND 或 OR 在 WHERE 子句中,这样更容易。
使用这些条件对象添加额外的限制条件会更容易。
让我们来看一个简单的例子,在这个例子中,我们将创建一个查询并通过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,所以如果名字被重构和重命名,则使用QueryOver的链接风格条件查询中的更改也会生效。
我们仍然可以做很多类似的事情,但不能在QueryOver中使用查询理解语法,必须使用方法链语法,并且不能混合使用链接和条件。
对于许多查询,QueryOver API非常有用,并且提供了比直接使用条件更容易理解的对象语法。
让我们来看一个简单的例子,在这个例子中,我们将使用QueryOver检索名字为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; } } }
正如你所看到的,它在底层仍然是条件,但只是一个更友好的语法。
当以上代码编译并执行时,你将看到以下输出。
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方法是什么,所以你将得到一个运行时异常。
异常表示无法识别的调用方法。在这里,我们正在做显而易见的事情,但它不一定能工作。
让我们尝试其他方法,比如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 Profiler。
正如你所看到的,名字等于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或条件,看看是否更合适。
它只是提供了不同的语法,因此条件(create 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的Object数组,其中包含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完成其余工作。
让我们来看一个简单的例子,创建一个新的控制台项目。在本章中,我们将使用一个简单的数据库,其中包含一个简单的Customer表,如下面的图片所示。
安装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; } } }
现在我们需要使用Fluent 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 + "\t" + 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个客户。