NHibernate – 快速指南
NHibernate – 快速指南
NHibernate – 概述
在本章中,我们将讨论什么是 NHibernate,它可以在哪些平台上实现,它的优势是什么以及与它相关的其他方面。
什么是NHibernate?
NHibernate 是一个成熟的开源对象关系映射器,用于 .NET 框架。它被积极开发、功能齐全并用于数千个成功的项目。它建立在ADO.NET之上,当前版本是NHibernate 4.0.4。
-
NHibernate 是一个开源的 .NET 对象关系映射器,根据GNU 宽松通用公共许可证分发。
-
它基于 Hibernate,Hibernate 是一种流行的 Java 对象关系映射器,并且具有非常成熟和活跃的代码库。
-
它提供了一个将面向对象的领域模型映射到传统关系数据库的框架。
-
NHibernate 是由Tom Barrett 发起的,这个项目从 2003 年 2 月开始,这是他们的第一次提交。
-
这是一个大项目,提供了很多功能。
-
有一个NuGet 包可用,这使得添加到项目中变得非常容易。
为什么是NHibernate?
现在的问题是为什么我们需要对象关系映射器?这是因为对象世界和关系世界之间存在脱节。
-
在对象世界中,一切都基于对象;我们称对象为那些拥有我们数据的东西。
-
关系世界都是基于集合的,我们正在处理与对象世界不同的表和行。
-
在对象世界中,我们有单向关联。如果客户有一个指向订单的指针,并不一定意味着订单有一个指向客户的指针,它可能会也可能不会。
-
在关系世界中,所有关联都是双向的,可以通过外键完成。
-
所有的关联本质上都是双向的,所以当我们在处理对象-关系映射时,我们也需要处理这种脱节。
-
在对象世界中,我们使用的是单向指针,而在关系世界中,我们使用的是本质上是双向的外键。
-
对象世界有继承的概念,其中一辆汽车可以有许多不同的子类,所以汽车是一种交通工具,船是一种交通工具,跑车是一种汽车,这些类型继承关系。
-
关系世界没有这种继承的概念。
映射
那么我们如何映射所有这些不相交的关系呢?这个映射概念来自对象关系映射器。如下图所示,主要需要理解三件事。
-
在您的应用程序中,您将需要类定义,通常是 C# 代码及其代表我们的类的 .NET 代码,例如 Employee 类、Customer 类、Order 类等。
-
在底部,您可以看到一个数据库模式,它是我们在关系数据库中的数据定义语言,它指定了客户表的外观,员工表的外观。
-
在这些之间,我们有映射元数据,它告诉对象关系映射器如何根据行和列以及外键关系从 C# 中的对象世界转换到数据库世界。
-
这种映射元数据可以用多种不同的方式表示,我们将研究 NHibernate 应用程序中最典型的多种不同方式。
-
它由HBM(Hibernate Mapping)文件表示,这些文件是 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。
-
一列是员工姓名,另一列是他们的加入日期,最后是一列包含员工年龄的列。
-
如果我们想编写一些代码来在表中存储一个新员工,这并不容易。
-
在上图中,您还可以看到我们有一个员工对象,其中包含 Id、姓名、加入日期和年龄字段。
-
如果没有 ORM,我们必须将这个对象转换成几个不同的 SQL 语句,这些语句将员工数据插入到员工表中。
-
所以编写代码来创建 SQL 来完成上述场景并不难,但有点乏味,而且很容易出错。
-
使用像 NHibernate 这样的 ORM,我们可以声明某些类应该如何映射到关系表,并让 ORM 或 NHibernate 处理创建 SQL 以在我们的员工表中查询数据中插入、更新、删除的繁琐工作。
-
这使我们能够让我们的代码专注于使用对象,并让这些对象自动转换为关系表。
-
所以 ORM 的真正作用是让我们免于手动将对象映射到表。
NHibernate – 环境设置
要开始使用 NHibernate,我们需要 Visual Studio 和 NHibernate 包。
Visual Studio 安装
微软提供了一个免费版本的 Visual Studio,其中也包含SQL Server,可以从https://www.visualstudio.com下载。 以下是安装步骤。
步骤 1 – 下载完成后运行安装程序,将显示以下对话框。
步骤 2 – 单击安装按钮,它将开始安装过程。
步骤 3 – 安装过程成功完成后,您将看到以下对话框。
步骤 4 – 关闭此对话框并根据需要重新启动计算机。
第 5 步– 现在从开始菜单打开 Visual Studio,这将打开以下对话框。第一次准备需要一些时间。
第 6 步– 完成所有这些后,您将看到 Visual Studio 的主窗口。
NHibernate 包安装
NHibernate 是一个成熟的开源对象关系映射器,用于 .NET 框架。它被积极开发、功能齐全并用于数千个成功的项目。您可以使用以下方法安装 NHibernate 包。
直接下载
-
从包含所有所需二进制文件的https://sourceforge.net/文件下载 zip 。
-
解压缩此 zip 文件并将所有这些二进制文件包含在您的项目中。
使用 NuGet 安装
-
安装 NHibernate 的另一种方法是使用 NuGet 安装 NHibernate 包,这是迄今为止将 NHibernate 合并到项目中的最简单方法。
-
它将下载所有 NHibernate 依赖项并创建对所有必需程序集的引用。
-
要安装 NHibernate,请在包管理器控制台中运行以下命令。
install-package NHibernate
您现在可以开始您的应用程序了。
NHibernate – 入门
在本章中,我们将看看如何使用 NHibernate 启动一个简单的示例。我们将构建一个简单的控制台应用程序。要创建控制台应用程序,我们将使用 Visual Studio 2015,它包含您需要创建的所有功能,并使用 NHibernate 包测试您的应用程序。
以下是使用 Visual Studio 中可用的项目模板创建项目的步骤。
步骤 1 – 打开 Visual Studio,然后单击文件 → 新建 → 项目菜单选项。
步骤 2 – 一个新的项目对话框打开。
步骤 3 – 从左窗格中,选择模板 → Visual C# → Windows。
步骤 4 – 在中间窗格中,选择控制台应用程序。
第 5 步– 在“名称”字段中输入项目名称“NHibernateDemoApp”,然后单击“确定”继续。
步骤 6 – 一旦 Visual Studio 创建了项目,您将在解决方案资源管理器窗口中看到许多文件。
如您所知,我们已经创建了一个简单的控制台应用程序项目,现在我们需要将 NHibernate 包包含到我们的控制台项目中。
转到工具菜单并选择 NuGet 包管理器 → 包管理器控制台,它将打开包管理器控制台窗口。
指定上面包管理器控制台窗口中显示的命令并按回车键,它将下载所有 NHibernate 依赖项并创建对所有必需程序集的引用。安装完成后,您将看到如下图所示的消息。
现在我们已经添加了 NHibernate,我们现在可以开始实现了。因此,我们将首先映射一个名为Student的非常简单的表,它只有一个名为 ID 的整数主键以及一个 FirstName 和 LastName 列。
我们需要一个类来代表这个学生,所以让我们通过右键单击解决方案资源管理器中的项目创建一个名为 Student 的新类,然后选择 Add → Class 这将打开 Add New Item 对话框。
在名称字段中输入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 对象资源管理器中的数据库并选择属性。
它将打开“属性”窗口,现在向下滚动,您将在“属性”窗口中看到“连接字符串”字段。
复制连接字符串并在您的代码中指定。以下是我们需要配置NHibernate的Main方法的实现。
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>
接下来我们需要定义一个类;这个班级将成为我们的学生班级。接下来,我们需要告诉 NHibernate id 的名称,也就是 ID,我还必须告诉 NHibernate 如何生成 ID,所以我们的生成器将是 native 类型的。
本机类型生成器意味着在像 SQL Server 这样的数据库中,它将使用标识列,即标识类型。
接下来我们要做的是给出属性的名称。因此,为 FirstName 和 LastName 添加另外两个属性。
现在,我们正在从程序集中读取这些映射文件。因此,执行此操作的首选方法是将这些HBM 文件烘焙到您的程序集中。我们可以通过简单地设置一个属性来做到这一点。
现在右键单击解决方案资源管理器中的项目并选择属性,您将看到默认选择内容的构建操作字段。
从下拉列表中选择嵌入的资源。
所以这实际上将该 XML 文件嵌入到NHibernateDemoApp程序集中。
NHibernate – 基本的 CRUD 操作
在本章中,我们将介绍基本的CRUD 操作。现在我们的系统已经准备好启动,因为我们已经成功地实现了我们的域 Student 类,我们还定义了映射文件并配置了 NHibernate。我们现在可以使用一些查询来执行 CRUD 操作。
创建数据
如您所见,NHibernateDemoDB数据库中的 Student 表中没有数据。
所以要添加一些数据,我们需要执行如下所示的添加/创建操作。
using (var session = sefact.OpenSession()) { using (var tx = session.BeginTransaction()) { var student1 = new Student { ID = 1, FirstMidName = "Allan", LastName = "Bommer" }; var student2 = new Student { ID = 2, FirstMidName = "Jerry", LastName = "Lewis" }; session.Save(student1); session.Save(student2); tx.Commit(); } Console.ReadLine(); }
如您所见,我们创建了两个学生,然后调用OpenSession的 Save() 方法,然后调用BeginTransaction的 Commit() 。这是Program.cs文件中的完整实现
using NHibernate.Cfg; using NHibernate.Dialect; using NHibernate.Driver; using System; using System.Linq; using System.Reflection; namespace NHibernateDemoApp { class Program { static void Main(string[] args) { var cfg = new Configuration(); String Data Source = asia13797\\sqlexpress; String Initial Catalog = NHibernateDemoDB; String Integrated Security = True; String Connect Timeout = 15; String Encrypt = False; String TrustServerCertificate = False; String ApplicationIntent = ReadWrite; String MultiSubnetFailover = False; cfg.DataBaseIntegration(x = > { x.ConnectionString = "Data Source + Initial Catalog + Integrated Security + Connect Timeout + Encrypt + TrustServerCertificate + ApplicationIntent + MultiSubnetFailover"; x.Driver<SqlClientDriver>(); x.Dialect<MsSql2008Dialect>(); }); cfg.AddAssembly(Assembly.GetExecutingAssembly()); var sefact = cfg.BuildSessionFactory(); using (var session = sefact.OpenSession()) { using (var tx = session.BeginTransaction()) { var student1 = new Student { ID = 1, FirstMidName = "Allan", LastName = "Bommer" }; var student2 = new Student { ID = 2, FirstMidName = "Jerry", LastName = "Lewis" }; session.Save(student1); session.Save(student2); tx.Commit(); } Console.ReadLine(); } } } }
现在让我们运行这个应用程序,然后转到 SQL Server 对象资源管理器并刷新您的数据库。您将看到上面的两个学生现在已添加到 NHibernateDemoDB 数据库中的 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(); }
所以如果你想要记录列表,那么我们可以简单地说学生类型的列表。
现在对所有学生使用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 的姓氏从 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 – 分析器
在本章中,我们将了解如何检索、更新、创建和删除数据库中的所有记录,以及这些查询究竟是如何执行的?
为了理解所有这些,我们可以简单地在我们的配置中添加一个选项,它在控制台中记录 SQL。这是将记录 SQL 查询的简单语句 –
x.LogSqlInConsole = true;
现在,我们在 NHibernateDemoDB 数据库中的 student 表中有两条记录。让我们从数据库中检索所有记录,如以下代码所示。
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 分析器
查看这些结果的另一种方法是使用 NHibernate Profiler。NHibernate Profiler 是一个商业工具,但它对于处理 NHibernate 应用程序非常有用。您可以从 NuGet 轻松地将 NHibernate Profiler 安装到您的应用程序中。
让我们通过选择 NuGet 包管理器 → 包管理器控制台从工具菜单转到 NuGet 管理器控制台。它将打开包管理器控制台窗口。输入以下命令并按回车键。
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 文件)中。正如您在映射域 Student 类时所观察到的,目前我们没有可用的 IntelliSense。使用XML 模式非常有用。因此,在本章中,您将了解如何在 Visual Studio 中为这些 NHibernate XML 文件添加 IntelliSense。
打开映射文件,您将看到主菜单中出现 XML 菜单选项。
选择 XML → Schemas… 菜单选项,它将显示 XML Schemas 对话框。
选择对话框右上角的添加…按钮,打开文件对话框。现在转到项目的解决方案文件夹中的包文件夹,您将看到项目中包含的不同包。
现在,双击NHibernate.4.*** 文件夹,您将看到定义 NHibernate 配置和映射的两个模式 (*.xsd) 文件或 XML 模式定义文件。
选择这两个模式文件并单击打开按钮。
您可以看到 NHibernate 模式被添加到 XML 模式对话框中。单击确定按钮。现在,让我们开始一个新的属性标签,您将看到我们在此处拥有完整的 IntelliSense。
IntelliSense 现在可供您使用,这可以在对象关系映射期间节省大量时间。
NHibernate – 数据类型映射
在本章中,我们将介绍映射数据类型。映射实体很简单,实体类总是使用<class>、<subclass> 和 <joined-subclass>映射元素映射到数据库表。值类型需要更多东西,这就是需要映射类型的地方。
NHibernate 能够映射多种数据类型。以下是支持的最常见数据类型的列表。
Mapping type | .NET 类型 | 系统.Data.DbType |
---|---|---|
Int16 | System.Int16 | 数据库类型.Int16 |
Int32 | System.Int32 | 数据库类型.Int32 |
Int64 | System.Int64 | 数据库类型.Int64 |
Single | 单系统 | DbType.Single |
Double | 双系统 | DbType.Double |
Decimal | 系统.十进制 | DbType.Decimal |
String | 系统字符串 | 数据库类型.字符串 |
AnsiString | 系统字符串 | 数据库类型.AnsiString |
Byte | 系统字节 | 数据库类型.Byte |
Char | 系统字符 | DbType.StringFixedLength——一个字符 |
AnsiChar | 系统字符 | DbType.AnsiStringFixedLength——一个字符 |
Boolean | 系统布尔值 | DbType.Boolean |
Guid | 系统向导 | 数据库类型.Guid |
PersistentEnum | System.Enum(一个枚举) | 基础值的 DbType |
TrueFalse | 系统布尔值 | DbType.AnsiStringFixedLength – ‘T’ 或 ‘F’ |
YesNo | 系统布尔值 | DbType.AnsiStringFixedLength – ‘Y’ 或 ‘N’ |
DateTime | 约会时间 | DbType.DateTime—忽略毫秒 |
Ticks | 系统日期时间 | 数据库类型.Int64 |
TimeSpan | 系统时间跨度 | 数据库类型.Int64 |
Timestamp | 系统日期时间 | DbType.DateTime — 与数据库支持的一样具体 |
Binary | System.Byte[] | 数据库类型.Binary |
BinaryBlob | System.Byte[] | 数据库类型.Binary |
StringClob | 系统字符串 | 数据库类型.字符串 |
Serializable | 任何用 SerializableAttribute 标记的 System.Object | 数据库类型.Binary |
CultureInfo | 系统.全球化.文化信息 | DbType.String——五个字符用于文化 |
Type | 系统类型 | 保存程序集限定名称的 DbType.String |
上面给出的表格详细解释了下面提到的指针。
-
从简单的数字类型到字符串,一切都可以使用varchars、chars等以多种方式映射,以及字符串 blob 和数据库支持的所有各种类型。
-
它还能够将Booleans映射到使用零和一的字段、包含真、假或 T 和 F 的字符字段。
-
有多种方法可以定义如何映射到数据库中的后端布尔值。
-
我们可以处理DateTime的映射,包括和不包括时区偏移、夏令时等。
-
我们还可以映射枚举;我们可以将这些映射到字符串或它们的底层数值。
让我们看一个简单的例子,在这个例子中,我们在数据库和 Student 类中都有相同的属性名称。
现在让我们在学生类中将 FirstMidName 更改为 FirstName,这里我们不会更改 FirstMidName 列,但我们将看到如何告诉 NHibernate 知道执行此转换。以下是更新的学生类。
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace NHibernateDemoApp { class Student { public virtual int ID { get; set; } public virtual string LastName { get; set; } public virtual string FirstName { get; set; } } }
这里是NHibernate映射文件的实现。
<?xml version = "1.0" encoding = "utf-8" ?> <hibernate-mapping xmlns = "urn:nhibernate-mapping-2.2" assembly = "NHibernateDemoApp" namespace = "NHibernateDemoApp"> <class name = "Student"> <id name = "ID"> <generator class = "native"/> </id> <property name = "LastName"/> <property name = "FirstName" column = "FirstMidName" type = "String"/> </class> </hibernate-mapping>
在此示例中,假设 FirstName 字段是 .NET 字符串,FirstMidName 列是SQL nvarchar。现在告诉 NHibernate 如何进行这种转换,将 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 类中添加另一个属性。这是 Student 类的实现。
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace NHibernateDemoApp { class Student { public virtual int ID { get; set; } public virtual string LastName { get; set; } public virtual string FirstName { get; set; } public virtual StudentAcademicStanding AcademicStanding { get; set; } } public enum StudentAcademicStanding { Excellent, Good, Fair, Poor, Terrible } }
如您所见,枚举具有各种不同的值,可能具有诸如优秀、良好、一般、差和可怕等。
跳转到映射文件,您可以看到映射文件中列出了这些属性中的每一个,包括新添加的AcademicStanding属性。
<?xml version = "1.0" encoding = "utf-8" ?> <hibernate-mapping xmlns = "urn:nhibernate-mapping-2.2" assembly = "NHibernateDemoApp" namespace = "NHibernateDemoApp"> <class name = "Student"> <id name = "ID"> <generator class = "native"/> </id> <property name = "LastName"/> <property name = "FirstName" column = "FirstMidName" type = "String"/> <property name = "AcademicStanding"/> </class> </hibernate-mapping>
现在我们还需要更改数据库,因此转到 SQL Server 对象资源管理器并右键单击数据库并选择新建查询…选项。
它将打开查询编辑器,然后指定以下查询。
DROP TABLE [dbo].[Student] CREATE TABLE [dbo].[Student] ( [ID] INT IDENTITY (1, 1) NOT NULL, [LastName] NVARCHAR (MAX) NULL, [FirstMidName] NVARCHAR (MAX) NULL, [AcademicStanding] NCHAR(10) NULL, CONSTRAINT [PK_dbo.Student] PRIMARY KEY CLUSTERED ([ID] ASC) );
此查询将首先删除现有的学生表,然后创建一个新表。
单击 Execute 图标,如上所示。成功执行查询后,您会看到一条消息。
展开数据库和表下拉列表,然后右键单击学生表并选择查看设计器。
现在,您将看到新创建的表,该表还具有新属性 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
现在让我们通过右键单击学生表来查看数据库。
选择查看数据,您将在学生表中看到两条记录,如下面的屏幕截图所示。
您可以看到添加了两条记录,Allan 的 AcademicStanding 为 0,Jerry 的 AcademicStanding 为 1。这是因为在 .Net 中,默认第一个枚举值是 0,如果您查看StudentAcademicStanding,这是非常好的。而在 Student.cs 文件中 Good 是第二个,所以它的值为 1。
NHibernate – 配置
在本章中,我们将看看 NHibernate 的配置。我们有不同的方式来配置 NHibernate。它分为两个主要组
- 基于 XML 的配置
- 基于代码的配置
基于代码的配置
基于代码的配置内置于 NHibernate 中。它是围绕 NHibernate 3 引入的,到目前为止我们一直使用代码库配置。
String Data Source = asia13797\\sqlexpress; String Initial Catalog = NHibernateDemoDB; String Integrated Security = True; String Connect Timeout = 15; String Encrypt = False; String TrustServerCertificate = False; String ApplicationIntent = ReadWrite; String MultiSubnetFailover = False; cfg.DataBaseIntegration(x = > { x.ConnectionString = "Data Source + Initial Catalog + Integrated Security + Connect Timeout + Encrypt + TrustServerCertificate + ApplicationIntent + MultiSubnetFailover"; x.Driver<SqlClientDriver>(); x.Dialect<MsSql2008Dialect>(); x.LogSqlInConsole = true; }); cfg.AddAssembly(Assembly.GetExecutingAssembly());
所有配置都在 C# 代码中指定。在这里可以看到我们得到了新的配置对象,然后我们使用NHibernate 3.1引入的loquacious配置来配置数据库。我们正在使用什么连接字符串,我们要连接到什么数据库以及要使用的方言。我们还将我们的映射程序集直接添加到此处。
基于 XML 的配置
如果您使用基于 XML 的配置,您可以使用hibernate.cfg.xml文件,它只是一个使用 NHibernate 模式的独立 xml 文件,或者您可以将该 NHibernate 特定配置嵌入到您的应用程序或web.cfg 中。hibernate.cfg.xml 名称是默认的,但我们也可以为该 xml 文件使用任意名称。
让我们通过向 NHibernateDemoApp 项目添加一个新的 xml 文件并将其命名为 hibernate.cfg.xml 来了解基于 XML 的配置。
在 hibernate.cfg.xml 文件中输入以下信息。
<?xml version = "1.0" encoding = "utf-8" ?> <hibernate-configuration xmlns = "urn:nhibernate-configuration-2.2"> <session-factory> <property name = "connection.connection_string"> Data Source = asia13797\\sqlexpress; Initial Catalog = NHibernateDemoDB; Integrated Security = True; Connect Timeout = 15; Encrypt = False; TrustServerCertificate = False; ApplicationIntent = ReadWrite; MultiSubnetFailover = False; </property> <property name = "connection.driver_class"> NHibernate.Driver.SqlClientDriver </property> <property name = "dialect"> NHibernate.Dialect.MsSql2008Dialect </property> <mapping assembly = "NHibernateDemoApp"/> </session-factory> </hibernate-configuration>
正如您在上面的 xml 文件中看到的,我们指定了与 C# 中提到的相同的配置。
现在让我们从 Program.cs 文件中评论这个配置,只需调用Configure()方法,它将加载hibernate.cfg.xml文件,如下所示。
using HibernatingRhinos.Profiler.Appender.NHibernate; using NHibernate.Cfg; using NHibernate.Dialect; using NHibernate.Driver; using System; using System.Linq; using System.Reflection; namespace NHibernateDemoApp { class Program { static void Main(string[] args) { NHibernateProfiler.Initialize(); var cfg = new Configuration(); //cfg.DataBaseIntegration(x => //{ // x.ConnectionString = "Data Source = asia13797;\\sqlexpress Initial Catalog = NHibernateDemoDB; Integrated Security = True; Connect Timeout = 15; Encrypt =False; TrustServerCertificate = False; ApplicationIntent = ReadWrite; MultiSubnetFailover = False"; // x.Driver<SqlClientDriver>(); // x.Dialect<MsSql2008Dialect>(); // x.LogSqlInConsole = true; //}); //cfg.AddAssembly(Assembly.GetExecutingAssembly()); cfg.Configure(); var sefact = cfg.BuildSessionFactory(); using (var session = sefact.OpenSession()) { using (var tx = session.BeginTransaction()) { var students = session.CreateCriteria<Student>().List<Student>(); Console.WriteLine("\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 文件,也不必只使用基于代码的配置或流畅的 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 需要做一个选择范围标识,因为我们在 ID 的映射文件中使用本机标识符生成策略,如下面的代码所示。
<?xml version = "1.0" encoding = "utf-8" ?> <hibernate-mapping xmlns = "urn:nhibernate-mapping-2.2" assembly = "NHibernateDemoApp" namespace = "NHibernateDemoApp"> <class name = "Student"> <id name = "ID"> <generator class = "native"/> </id> <property name = "LastName"/> <property name = "FirstName" column = "FirstMidName" type = "String"/> <property name = "AcademicStanding"/> </class> </hibernate-mapping>
所以我们需要使用不同的方法,比如guid.comb方法。如果我们要去 guid.comb,我们需要转到我们的客户并将其更改为guid。这样就可以正常工作了。现在让我们使用以下代码从本机更改为 guid.comb。
<?xml version = "1.0" encoding = "utf-8" ?> <hibernate-mapping xmlns = "urn:nhibernate-mapping-2.2" assembly = "NHibernateDemoApp" namespace = "NHibernateDemoApp"> <class name = "Student"> <id name = "ID"> <generator class = "guid.comb"/> </id> <property name = "LastName"/> <property name = "FirstName" column = "FirstMidName" type = "String"/> <property name = "AcademicStanding"/> </class> </hibernate-mapping>
因此,数据库负责生成这些 ID。NHibernate 可以找出生成的 ID 的唯一方法是在之后立即选择它。否则,如果我们创建了一批学生,将无法匹配创建的学生的ID。
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace NHibernateDemoApp { class Student { public virtual Guid ID { get; set; } public virtual string LastName { get; set; } public virtual string FirstName { get; set; } public virtual StudentAcademicStanding AcademicStanding { get; set; } } public enum StudentAcademicStanding { Excellent, Good, Fair, Poor, Terrible } }
我们只需要更新我们的数据库。让我们删除 student 表并通过指定以下查询创建一个新表,因此转到 SQL Server 对象资源管理器并右键单击数据库并选择New Query … 选项。
它将打开查询编辑器,然后指定以下查询。
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,同时插入数据,因为现在它会自动为其生成guid。
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 profiler 而不是进行 26 次往返将只进行 4 次。
它在表中插入了十行,然后是另外十行,然后是剩下的五行。在提交之后,它又插入了一个用于检索所有记录。
-
所以它尽可能将它分成十个一组。
-
因此,如果您要执行大量插入操作,这可以显着提高应用程序中的插入性能,因为您可以将其进行批处理。
-
这是因为 NHibernate 使用guid.comb算法自己分配这些 guid ,它不必依赖数据库来执行此操作。
-
所以使用批量大小是一个很好的调整方式。
NHibernate – 缓存
在本章中,我们将介绍缓存在 NHibernate 应用程序中是如何工作的。它具有内置的缓存支持。它看起来是一个简单的功能,但实际上,它是最复杂的功能之一。我们将从一级缓存开始。
一级缓存
这个缓存机制在 NHibernate 中默认是启用的,我们不需要为使用缓存做任何事情。为了理解这一点,让我们看一个简单的例子,你可以看到我们的数据库中有两条记录。
现在在这个例子中,我们将检索 ID 为 1 的学生,我们将使用相同的会话查询两次,如下面的代码所示。
using HibernatingRhinos.Profiler.Appender.NHibernate; using NHibernate.Cache; using NHibernate.Cfg; using NHibernate.Dialect; using NHibernate.Driver; using NHibernate.Linq; using System; using System.Linq; using System.Reflection; namespace NHibernateDemoApp { class Program { static void Main(string[] args) { NHibernateProfiler.Initialize(); var cfg = new Configuration(); String Data Source = asia13797\\sqlexpress; String Initial Catalog = NHibernateDemoDB; String Integrated Security = True; String Connect Timeout = 15; String Encrypt = False; String TrustServerCertificate = False; String ApplicationIntent = ReadWrite; String MultiSubnetFailover = False; cfg.DataBaseIntegration(x = > { x.ConnectionString = "Data Source + Initial Catalog + Integrated Security + Connect Timeout + Encrypt + TrustServerCertificate + ApplicationIntent + MultiSubnetFailover"; x.Driver<SqlClientDriver>(); x.Dialect<MsSql2008Dialect>(); x.LogSqlInConsole = true; x.BatchSize = 10; }); //cfg.Configure(); cfg.Cache(c => { c.UseMinimalPuts = true; c.UseQueryCache = true; }); cfg.SessionFactory().Caching .Through<HashtableCacheProvider>() .WithDefaultExpiration(1440); cfg.AddAssembly(Assembly.GetExecutingAssembly()); var sefact = cfg.BuildSessionFactory(); using (var session = sefact.OpenSession()){ using (var tx = session.BeginTransaction()) { var studentUsingTheFirstQuery = session.Get<Student>(1); var studentUsingTheSecondQuery = session.Get<Student>(1); } Console.ReadLine(); } } } }
现在让我们运行这个应用程序并在 NHibernate Profiler 中查看结果。
您会惊讶地发现 NHibernate 只触发一个查询。这就是 NHibernate 使用一级缓存的方式。当执行第一个查询时,NHibernate 将 ID = 1 的 Student 缓存在其一级缓存中。
因此,当执行第二个查询时,NHibernate 首先查找 ID = 1 的一级缓存 Student 实体,如果找到该实体,则 NHibernate 知道,无需再次触发另一个查询来检索相同的员工对象.
NHibernate – 映射组件
在本章中,我们将讨论映射组件。在 NHibernate 中,组件是一个值对象。它没有自己的身份。
-
一个例子是钱对象,钱包或钱包里可能有钱,但钱的确切身份是无关紧要的。
-
它没有自己的主键,但组件本身与拥有的对象在同一个表中是持久的。
让我们看一个简单的例子,其中一个学生有一个 Address,它是一个与它关联的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 是一个类,它不是一个实体。
-
它是一个值类型对象,它没有自己的主键。
-
它与包含它的学生保存在同一个表中。
-
这就是我们在这里使用组件的原因。
-
这允许很大的灵活性来改变我们的类层,我们的类是如何定义的,而不是我们的数据库是如何布局的。
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
它将在数据库中创建两个表。下图显示了客户表。
下图显示了订单表,您可以在其中看到返回到客户的外键关系。
我们需要在app.config文件中定义连接字符串,这里是app.config文件的实现。
<?xml version = "1.0" encoding = "utf-8" ?> <configuration> <connectionStrings> <add name = "default" connectionString = "Data Source = (localdb)\MSSQLLocalDB;Initial Catalog = NHibernateDemo;Integrated Security = True;Connect Timeout = 30;Encrypt = False;TrustServerCertificate = False; ApplicationIntent = ReadWrite;MultiSubnetFailover = False"/> </connectionStrings> </configuration>
要在应用程序中安装 NHibernate,请在 NuGet 管理器控制台窗口中运行以下命令。
install-package NHibernate
要配置 NHibernate 配置,我们需要在hibernate.cfg.xml文件中定义配置,如以下代码所示。
<xml version = "1.0" encoding = "utf-8" ?> <hibernate-configuration xmlns = "urn:nhibernate-configuration-2.2"> <session-factory> <property name = "connection.connection_string_name">default</property> <property name = "connection.driver_class"> NHibernate.Driver.SqlClientDriver </property> <property name = "dialect"> NHibernate.Dialect.MsSql2008Dialect </property> <property name = "show_sql">true</property> </session-factory> </hibernate-configuration>
在这个例子中,我们将使用两个域类,Customer 和 Order。
这是 Customer.cs 文件实现,其中我们有两个类,一个是 Customer 类,另一个是 Location 类,其中对象用作 Customer 类中的地址。
using System; using System.Text; using Iesi.Collections.Generic; namespace NHibernateDemo { public class Customer { public Customer() { MemberSince = DateTime.UtcNow; Orders = new HashedSet<Order>(); } public virtual Guid Id { get; set; } public virtual string FirstName { get; set; } public virtual string LastName { get; set; } public virtual double AverageRating { get; set; } public virtual int Points { get; set; } public virtual bool HasGoldStatus { get; set; } public virtual DateTime MemberSince { get; set; } public virtual CustomerCreditRating CreditRating { get; set; } public virtual Location Address { get; set; } public virtual ISet<Order> Orders { get; set; } public virtual void AddOrder(Order order) { Orders.Add(order); order.Customer = this; } public override string ToString() { var result = new StringBuilder(); result.AppendFormat("{1} {2} ({0})\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文件,添加一个多对一,然后用customer ID命名customer字段和列。
<?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 来表示它。
集合可能是最常见的,但根据您的应用程序,您也会看到列表和包。让我们看一下上一章中定义了 Set 订单的以下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`"> <key column = "CustomerId"/> <one-to-many class = "Order"/> </set> </class> </hibernate-mapping>
如您所见,我们已将订单集合映射为一个集合。请记住,集合是唯一元素的无序集合。
现在,如果您查看 Customer 类,您将看到 Orders 属性是使用 ISet 定义的,如下面的程序所示。
public virtual ISet<Order> Orders { get; set; }
现在,当此应用程序运行时,您将看到以下输出。
New Customer: John Doe (00000000-0000-0000-0000-000000000000) Points: 100 HasGoldStatus: True MemberSince: 1/1/2012 12:00:00 AM (Unspecified) CreditRating: Good AverageRating: 42.42424242 Orders: Order Id: 00000000-0000-0000-0000-000000000000 Order Id: 00000000-0000-0000-0000-000000000000 Reloaded: John Doe (1f248133-b50a-4ad7-9915-a5b8017d0ff1) Points: 100 HasGoldStatus: True MemberSince: 1/1/2012 12:00:00 AM (Utc) CreditRating: Good AverageRating: 42.4242 Orders: Order Id: c41af8f2-7124-42a7-91c5-a5b8017d0ff6 Order Id: 657f6bb0-1f42-45fc-8fc7-a5b8017d0ff7 The orders were ordered by: John Doe (1f248133-b50a-4ad7-9915-a5b8017d0ff1) Points: 100 HasGoldStatus: True MemberSince: 1/1/2012 12:00:00 AM (Utc) CreditRating: Good AverageRating: 42.4242 Orders: Order Id: c41af8f2-7124-42a7-91c5-a5b8017d0ff6 Order Id: 657f6bb0-1f42-45fc-8fc7-a5b8017d0ff7 John Doe (1f248133-b50a-4ad7-9915-a5b8017d0ff1) Points: 100 HasGoldStatus: True MemberSince: 1/1/2012 12:00:00 AM (Utc) CreditRating: Good AverageRating: 42.4242 Orders: Order Id: c41af8f2-7124-42a7-91c5-a5b8017d0ff6 Order Id: 657f6bb0-1f42-45fc-8fc7-a5b8017d0ff7 Press <ENTER> to exit...
如果集合中的项目不需要是唯一的,如果您可以在此集合中多次出现具有相同主键的多个订单,那么这将更好地映射为一个包,如下面的程序所示。
<bag name = "Orders" table = "`Order`"> <key column = "CustomerId"/> <one-to-many class = "Order"/> </bag>
现在,如果您运行此应用程序,您将收到一个异常,因为如果我们查看客户类,您会注意到订单在 C# 代码中被标记为 ISet。
因此,我们还需要将其更改为 IList,然后在这里,我们需要在构造函数中从 HashSet 更改为 List。
public class Customer { public Customer() { MemberSince = DateTime.UtcNow; Orders = new List<Order>(); } public virtual Guid Id { get; set; } public virtual string FirstName { get; set; } public virtual string LastName { get; set; } public virtual double AverageRating { get; set; } public virtual int Points { get; set; } public virtual bool HasGoldStatus { get; set; } public virtual DateTime MemberSince { get; set; } public virtual CustomerCreditRating CreditRating { get; set; } public virtual Location Address { get; set; } public virtual IList<Order> Orders { get; set; } public virtual void AddOrder(Order order) { Orders.Add(order); order.Customer = this; } public override string ToString() { var result = new StringBuilder(); result.AppendFormat("{1} {2} ({0})\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 – 级联
在本章中,我们将介绍如何使用级联功能。如果您有一组或一组项目或两个类(例如我们的客户和订单)之间的关系并且具有外键关系。如果我们默认删除客户,NHibernate 不会对子对象执行任何操作,因此属于该客户的那些和我们可能是孤立订单。
-
我们也可能违反外键约束,所以我们可以使用级联的概念。
-
默认情况下,NHibernate 不会将操作级联到子对象。
-
这样做的原因是您可以建立关系,例如客户具有默认送货地址并且该送货地址与许多不同的客户共享。
-
因此,您不会希望级联这种关系,因为其他客户仍在参考它。
-
所以级联的整个概念是告诉 NHibernate 如何处理它的子实体。
级联有不同的选项,如下所示 –
-
none – 这是默认值,这意味着没有级联。
-
all – 将级联保存、更新和删除。
-
save-update – 它将级联,保存和更新。
-
delete – 它将级联删除。
-
all-delete-orphan – 它是一种非常常用的特殊行,与 All except 相同,如果它找到 Delete-orphan 行,它也会删除那些行。
您可以在hbm.xml文件中指定默认值,因此您可以在该 Hibernate 映射元素上提供默认级联,或者您也可以为特定集合和关系(例如多对一)指定它。
让我们看一下简单的级联示例,让我们解决程序中的问题,我们必须手动级联保存到订单,如下面的代码所示。
using(var session = sessionFactory.OpenSession()) using(var tx = session.BeginTransaction()) { var newCustomer = CreateCustomer(); Console.WriteLine("New Customer:"); Console.WriteLine(newCustomer); session.Save(newCustomer); foreach (var order in newCustomer.Orders) { session.Save(order); } id = newCustomer.Id; tx.Commit(); }
在上面的代码片段中,您可以看到我们正在手动保存客户的所有订单。现在让我们删除保存所有订单的手动级联代码。
using(var session = sessionFactory.OpenSession()) using(var tx = session.BeginTransaction()) { var newCustomer = CreateCustomer(); Console.WriteLine("New Customer:"); Console.WriteLine(newCustomer); session.Save(newCustomer); id = newCustomer.Id; tx.Commit(); }
我们需要在customer.hbm.xml 中指定级联选项。
<?xml version = "1.0" encoding = "utf-8" ?> <hibernate-mapping xmlns = "urn:nhibernate-mapping-2.2" assembly = "NHibernateDemo" namespace = "NHibernateDemo"> <class name = "Customer"> <id name = "Id"> <generator class = "guid.comb"/> </id> <property name = "FirstName"/> <property name = "LastName"/> <property name = "AverageRating"/> <property name = "Points"/> <property name = "HasGoldStatus"/> <property name = "MemberSince" type = "UtcDateTime"/> <property name = "CreditRating" type = "CustomerCreditRatingType"/> <component name = "Address"> <property name = "Street"/> <property name = "City"/> <property name = "Province"/> <property name = "Country"/> </component> <set name = "Orders" table = "`Order`" cascade = "all-delete-orphan"> <key column = "CustomerId"/> <one-to-many class = "Order"/> </set> </class> </hibernate-mapping>
-
现在,订单完全属于客户。因此,如果客户从数据库中删除,我们这里的应用程序将要删除所有这些订单,包括任何可能已被孤立的订单。
-
它最终会进行删除。这样,它会说从订单表中删除,其中客户 ID 等于您要删除的客户。
-
所以你实际上可以级联这些删除。因此,使用All,它将执行保存、更新和删除操作。
现在,当您运行此应用程序时,您将看到以下输出。
New Customer: John Doe (00000000-0000-0000-0000-000000000000) Points: 100 HasGoldStatus: True MemberSince: 1/1/2012 12:00:00 AM (Unspecified) CreditRating: Good AverageRating: 42.42424242 Orders: Order Id: 00000000-0000-0000-0000-000000000000 Order Id: 00000000-0000-0000-0000-000000000000 Reloaded: John Doe (10b2a3d7-7fcf-483c-b1da-a5bb00b8512e) Points: 100 HasGoldStatus: True MemberSince: 1/1/2012 12:00:00 AM (Utc) CreditRating: Good AverageRating: 42.4242 Orders: Order Id: e6680e30-5b3b-4efa-b017-a5bb00b85133 Order Id: b03858e7-8c36-4555-8878-a5bb00b85134 The orders were ordered by: John Doe (10b2a3d7-7fcf-483c-b1da-a5bb00b8512e) Points: 100 HasGoldStatus: True MemberSince: 1/1/2012 12:00:00 AM (Utc) CreditRating: Good AverageRating: 42.4242 Orders: Order Id: e6680e30-5b3b-4efa-b017-a5bb00b85133 Order Id: b03858e7-8c36-4555-8878-a5bb00b85134 John Doe (10b2a3d7-7fcf-483c-b1da-a5bb00b8512e) Points: 100 HasGoldStatus: True MemberSince: 1/1/2012 12:00:00 AM (Utc) CreditRating: Good AverageRating: 42.4242 Orders: Order Id: e6680e30-5b3b-4efa-b017-a5bb00b85133 Order Id: b03858e7-8c36-4555-8878-a5bb00b85134 Press <ENTER> to exit...
如您所见,我们已经从手动级联的程序中删除了代码,我们的应用程序仍在运行。
因此,根据您的关系,您可能想要级联这些。现在,让我们来看看不同的级联关系。让我们转到Order.hbm.xml文件,我们可以级联这种多对一关系。
<?xml version = "1.0" encoding = "utf-8" ?> <hibernate-mapping xmlns = "urn:nhibernate-mapping-2.2" assembly = "NHibernateDemo" namespace = "NHibernateDemo"> <class name = "Order" table = "`Order`"> <id name = "Id"> <generator class = "guid.comb"/> </id> <property name = "Ordered"/> <property name = "Shipped"/> <component name = "ShipTo"> <property name = "Street"/> <property name = "City"/> <property name = "Province"/> <property name = "Country"/> </component> <many-to-one name = "Customer" column = "CustomerId" cascade = "save-update"/> </class> </hibernate-mapping>
因此,如果我们创建一个新订单并且有一个新客户附加到它,我们说,保存该订单,我们可能想要级联它。但是我们可能不想做的一件事是删除订单以删除相应的客户。
所以在这里,我们想要做一个保存更新,所以使用保存更新,它会将任何保存或更新级联到该客户。所以,如果我们有一个新客户,或者如果我们正在改变客户,它会级联。如果是删除,则不会将其从数据库中删除。
所以再次运行我们的应用程序,一切仍然按预期工作。
New Customer: John Doe (00000000-0000-0000-0000-000000000000) Points: 100 HasGoldStatus: True MemberSince: 1/1/2012 12:00:00 AM (Unspecified) CreditRating: Good AverageRating: 42.42424242 Orders: Id: 00000000-0000-0000-0000-000000000000 Order Id: 00000000-0000-0000-0000-000000000000 Reloaded: John Doe (10b2a3d7-7fcf-483c-b1da-a5bb00b8512e) Points: 100 HasGoldStatus: True MemberSince: 1/1/2012 12:00:00 AM (Utc) CreditRating: Good AverageRating: 42.4242 Orders: Order Id: e6680e30-5b3b-4efa-b017-a5bb00b85133 Order Id: b03858e7-8c36-4555-8878-a5bb00b85134 The orders were ordered by: John Doe (10b2a3d7-7fcf-483c-b1da-a5bb00b8512e) Points: 100 HasGoldStatus: True MemberSince: 1/1/2012 12:00:00 AM (Utc) CreditRating: Good AverageRating: 42.4242 Orders: Order Id: e6680e30-5b3b-4efa-b017-a5bb00b85133 Order Id: b03858e7-8c36-4555-8878-a5bb00b85134 John Doe (10b2a3d7-7fcf-483c-b1da-a5bb00b8512e) Points: 100 HasGoldStatus: True MemberSince: 1/1/2012 12:00:00 AM (Utc) CreditRating: Good AverageRating: 42.4242 Orders: Order Id: e6680e30-5b3b-4efa-b017-a5bb00b85133 Order Id: b03858e7-8c36-4555-8878-a5bb00b85134 Press <ENTER> to exit...
现在您应该查看您的应用程序,请记住默认值为 None 并且您必须考虑您的实体及其之间的关系,以确定您的每个实体以及该数据库中的每个关系的适当级联。
NHibernate – 延迟加载
在本章中,我们将介绍延迟加载功能。默认情况下这是一个完全不同的概念,并且 NHibernate 没有延迟加载,例如,如果您加载客户,它不会加载所有订单。
-
订单集合将按需加载。
-
任何关联,无论是多对一还是集合,默认情况下都是延迟加载的,它需要一个Open ISession。
-
如果您关闭了会话,或者提交了事务,您可能会收到延迟加载异常,它无法拉入这些额外的对象。
-
您必须小心延迟加载以及您实际需要的数据量。
-
您可以关闭整个关联的延迟加载,或者您可以设置延迟等于 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,您可以看到我们再次发生了这种 Eager join fetch,但这一次,它是基于查询的。
NHibernate – 反向关系
在本章中,我们将介绍另一个特征,即反向关系。这是一个有趣的选项,您会在集合中看到与 true 成反比的选项,它也会让很多开发人员感到困惑。那么让我们来谈谈这个选项。要理解这一点,您真的必须考虑关系模型。假设您有一个使用单个外键的双向关联。
-
从关系的角度来看,您有一个外键,它代表客户到订单和订单到客户。
-
在 OO 模型中,您可以使用这些引用进行单向关联。
-
没有什么说两个单向关联在数据库中代表相同的双向关联。
-
这里的问题是 NHibernate 没有足够的信息来知道customer.orders和order.customer在数据库中代表相同的关系。
-
我们需要提供inverse equals true作为提示,这是因为单向关联使用相同的数据。
-
如果我们尝试保存这些有 2 个引用的关系,NHibernate 将尝试更新该引用两次。
-
它实际上会对数据库进行一次额外的往返,并且还会对该外键进行 2 次更新。
-
inverse 等于 true 告诉 NHibernate 忽略关系的哪一侧。
-
当您将其应用于集合端时,NHibernate 将始终从子对象端的另一端更新外键。
-
然后我们对该外键只有一个更新,并且我们没有对该数据进行额外的更新。
-
这使我们能够防止对外键进行这些重复更新,还有助于我们防止外键违规。
让我们看看customer.cs文件,您将在其中看到AddOrder方法,这里的想法是我们现在有这个从订单返回到客户的返回指针,它需要设置。所以当一个订单被添加到一个客户时,该客户的后向指针被设置,否则,它将为空,所以我们需要它来保持它在对象图中正确连接在一起。
using System; using System.Text; using Iesi.Collections.Generic; namespace NHibernateDemo { public class Customer { public Customer() { MemberSince = DateTime.UtcNow; Orders = new HashedSet<Order>(); } public virtual Guid Id { get; set; } public virtual string FirstName { get; set; } public virtual string LastName { get; set; } public virtual double AverageRating { get; set; } public virtual int Points { get; set; } public virtual bool HasGoldStatus { get; set; } public virtual DateTime MemberSince { get; set; } public virtual CustomerCreditRating CreditRating { get; set; } public virtual Location Address { get; set; } public virtual ISet<Order> Orders { get; set; } public virtual void AddOrder(Order order) { Orders.Add(order); order.Customer = this; } public override string ToString() { var result = new StringBuilder(); result.AppendFormat("{1} {2} ({0})\r\n\tPoints: {3}\r\n\tHasGoldStatus: {4}\r\n\tMemberSince: {5} ({7})\r\n\tCreditRating: {6}\r\n\tAverageRating: {8}\r\n", Id, FirstName, LastName, Points, HasGoldStatus, MemberSince, CreditRating, MemberSince.Kind, AverageRating); result.AppendLine("\tOrders:"); foreach(var order in Orders) { result.AppendLine("\t\t" + order); } return result.ToString(); } } public class Location { public virtual string Street { get; set; } public virtual string City { get; set; } public virtual string Province { get; set; } public virtual string Country { get; set; } } public enum CustomerCreditRating { Excellent, VeryVeryGood, VeryGood, Good, Neutral, Poor, Terrible } }
这是Program.cs文件的实现。
using System; using System.Data; using System.Linq; using System.Reflection; using HibernatingRhinos.Profiler.Appender.NHibernate; using NHibernate.Cfg; using NHibernate.Dialect; using NHibernate.Driver; using NHibernate.Linq; namespace NHibernateDemo { internal class Program { private static void Main() { var cfg = ConfigureNHibernate(); var sessionFactory = cfg.BuildSessionFactory(); Guid id; using(var session = sessionFactory.OpenSession()) using(var tx = session.BeginTransaction()) { var newCustomer = CreateCustomer(); Console.WriteLine("New Customer:"); Console.WriteLine(newCustomer); session.Save(newCustomer); id = newCustomer.Id; tx.Commit(); } using(var session = sessionFactory.OpenSession()) using(var tx = session.BeginTransaction()) { var query = from customer in session.Query<Customer>() where customer.Id == id select customer; var reloaded = query.Fetch(x => x.Orders).ToList().First(); Console.WriteLine("Reloaded:"); Console.WriteLine(reloaded); tx.Commit(); } Console.WriteLine("Press <ENTER> to exit..."); Console.ReadLine(); } private static Customer CreateCustomer() { var customer = new Customer { FirstName = "John", LastName = "Doe", Points = 100, HasGoldStatus = true, MemberSince = new DateTime(2012, 1, 1), CreditRating = CustomerCreditRating.Good, AverageRating = 42.42424242, Address = CreateLocation() }; var order1 = new Order { Ordered = DateTime.Now }; customer.AddOrder(order1); var order2 = new Order { Ordered = DateTime.Now.AddDays(-1), Shipped = DateTime.Now, ShipTo = CreateLocation() }; customer.AddOrder(order2); return customer; } private static Location CreateLocation() { return new Location { Street = "123 Somewhere Avenue", City = "Nowhere", Province = "Alberta", Country = "Canada" }; } private static Configuration ConfigureNHibernate() { NHibernateProfiler.Initialize(); var cfg = new Configuration(); cfg.DataBaseIntegration(x => { x.ConnectionStringName = "default"; x.Driver<SqlClientDriver>(); x.Dialect<MsSql2008Dialect>(); x.IsolationLevel = IsolationLevel.RepeatableRead; x.Timeout = 10; x.BatchSize = 10; }); cfg.SessionFactory().GenerateStatistics(); cfg.AddAssembly(Assembly.GetExecutingAssembly()); return cfg; } } }
它将把它保存到数据库中,然后重新加载它。现在让我们运行您的应用程序并打开 NHibernate Profiler,看看它实际上是如何保存它的。
您会注意到我们有 3 组语句。第一个将插入客户,该客户的 ID 是 Guid,它突出显示。第二个语句是插入到订单表中。
您会注意到其中设置了相同的 Customer Id Guid,因此设置了该外键。最后一个语句是 update,它将再次将外键更新为相同的客户 ID。
现在的问题是客户有订单,订单有客户,我们不可能没有告诉NHibernate其实是同一种关系。我们这样做的方式是逆等于真。
因此,让我们转到我们的customer.hbm.xml映射文件,并将 inverse 设置为 true,如下面的代码所示。
<?xml version = "1.0" encoding = "utf-8" ?> <hibernate-mapping xmlns = "urn:nhibernate-mapping-2.2" assembly = "NHibernateDemo" namespace = "NHibernateDemo"> <class name = "Customer"> <id name = "Id"> <generator class = "guid.comb"/> </id> <property name = "FirstName"/> <property name = "LastName"/> <property name = "AverageRating"/> <property name = "Points"/> <property name = "HasGoldStatus"/> <property name = "MemberSince" type = "UtcDateTime"/> <property name = "CreditRating" type = "CustomerCreditRatingType"/> <component name = "Address"> <property name = "Street"/> <property name = "City"/> <property name = "Province"/> <property name = "Country"/> </component> <set name = "Orders" table = "`Order`" cascade = "all-delete-orphan" inverse = "true"> <key column = "CustomerId"/> <one-to-many class = "Order"/> </set> </class> </hibernate-mapping>
保存订单时,它将从订单端设置该外键。现在让我们再次运行这个应用程序并打开 NHibernate 分析器。
如果我们看看这些是如何插入的,我们会在客户中插入插入,并将插入到订单中,但是我们没有外键的重复更新,因为它在保存订单时正在更新。
-
现在,您应该注意,如果您只有一个单向关联并且它是维护这种关系的集合,那么如果您将 inverse 变为 true,则永远不会设置该外键,并且这些项目永远不会有它们的数据库中设置的外键。
-
如果您查看Order.hbm.xml文件中的多对一关系并查找 inverse,它实际上没有 inverse 属性。
-
它始终从子项设置,但如果您有一个多对多集合,则可以从任一侧设置它。
NHibernate – 加载/获取
在本章中,我们将介绍 Load 和 Get 功能的工作原理以及如何使用它们。这是ISession提供的两个非常相似的 API,用于通过主键加载对象。
-
Get – 它将返回对象或空值。
-
Load – 它将返回对象或抛出ObjectNotFoundException。
现在,为什么我们有这两个不同的 API?
加载
-
这是因为 Load 可以更有效地优化数据库往返。
-
Load 实际上返回一个代理对象,并且在您发出 Load 调用时不需要访问数据库。
-
当您访问该代理时,该对象恰好不在数据库中,此时它可能会抛出 ObjectNotFoundException。
得到
-
相反,由于 CLR 或公共语言运行时和 NHibernate的限制,使用 Get必须立即转到数据库,检查对象是否在那里,如果不存在则返回 null。
-
它没有延迟提取的对象选项,因为它无法返回代理对象并且在用户实际访问它时将该代理对象交换为空值。
让我们看一个简单的例子,在这个例子中你会看到这些是如何实际使用的,以及 Get 和 Load 之间的区别。我们将继续使用相同的域类Customer和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 窗口,您将看到两个对象的类型都是客户代理。您还可以在控制台窗口中看到 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...
让我们看另一个例子,在这个例子中,我们将检索所有那些名字以字母 H 开头的客户。
using System; using System.Data; using System.Linq; using System.Reflection; using HibernatingRhinos.Profiler.Appender.NHibernate; using NHibernate.Cfg; using NHibernate.Criterion; using NHibernate.Dialect; using NHibernate.Driver; using NHibernate.Linq; namespace NHibernateDemo { internal class Program { private static void Main() { var cfg = ConfigureNHibernate(); var sessionFactory = cfg.BuildSessionFactory(); using(var session = sessionFactory.OpenSession()) using(var tx = session.BeginTransaction()) { var customers = session.Query<Customer>() .Where(c =< c.FirstName.StartsWith("H")); foreach (var customer in customers.ToList()) { Console.WriteLine(customer); } tx.Commit(); } Console.WriteLine("Press <ENTER> to exit..."); Console.ReadLine(); } private static Configuration ConfigureNHibernate() { NHibernateProfiler.Initialize(); var cfg = new Configuration(); cfg.DataBaseIntegration(x => { x.ConnectionStringName = "default"; x.Driver<SqlClientDriver>(); x.Dialect<MsSql2008Dialect>(); x.IsolationLevel = IsolationLevel.RepeatableRead; x.Timeout = 10; x.BatchSize = 10; }); cfg.SessionFactory().GenerateStatistics(); cfg.AddAssembly(Assembly.GetExecutingAssembly()); return cfg; } } }
同样,查询理解语法将类似于以下程序。
using System; using System.Data; using System.Linq; using System.Reflection; using HibernatingRhinos.Profiler.Appender.NHibernate; using NHibernate.Cfg; using NHibernate.Criterion; using NHibernate.Dialect; using NHibernate.Driver; using NHibernate.Linq; namespace NHibernateDemo { internal class Program { private static void Main() { var cfg = ConfigureNHibernate(); var sessionFactory = cfg.BuildSessionFactory(); using(var session = sessionFactory.OpenSession()) using(var tx = session.BeginTransaction()) { var customers = from c in session.Query<Customer>() where c.FirstName.StartsWith("H") select c; foreach (var customer in customers.ToList()) { Console.WriteLine(customer); } tx.Commit(); } Console.WriteLine("Press <ENTER> to exit..."); Console.ReadLine(); } private static Configuration ConfigureNHibernate() { NHibernateProfiler.Initialize(); var cfg = new Configuration(); cfg.DataBaseIntegration(x => { x.ConnectionStringName = "default"; x.Driver<SqlClientDriver>(); x.Dialect<MsSql2008Dialect>(); x.IsolationLevel = IsolationLevel.RepeatableRead; x.Timeout = 10; x.BatchSize = 10; }); cfg.SessionFactory().GenerateStatistics(); cfg.AddAssembly(Assembly.GetExecutingAssembly()); return cfg; } } }
让我们再次运行此应用程序,您将看到名字以字母 H 开头的所有客户。
Herman Crooks (4ead3480-6bce-11e1-b15c-6cf049ee52be) Points: 74 HasGoldStatus: True MemberSince: 12/3/2010 12:00:00 AM (Utc) CreditRating: Neutral AverageRating: 0 Orders: Order Id: 4ead3480-6bce-11e1-b15d-6cf049ee52be Order Id: 4ead3480-6bce-11e1-b15e-6cf049ee52be Order Id: 4ead3480-6bce-11e1-b15f-6cf049ee52be Order Id: 4ead3480-6bce-11e1-b160-6cf049ee52be Order Id: 4ead3480-6bce-11e1-b161-6cf049ee52be Order Id: 4ead3480-6bce-11e1-b162-6cf049ee52be Order Id: 4ead3480-6bce-11e1-b163-6cf049ee52be Hudson Bins (4ec03f80-6bce-11e1-b2b7-6cf049ee52be) Points: 56 HasGoldStatus: False MemberSince: 10/20/2008 12:00:00 AM (Utc) CreditRating: Terrible AverageRating: 0 Orders: Order Id: 4ec03f80-6bce-11e1-b2b8-6cf049ee52be Order Id: 4ec03f80-6bce-11e1-b2b9-6cf049ee52be Order Id: 4ec03f80-6bce-11e1-b2ba-6cf049ee52be Order Id: 4ec03f80-6bce-11e1-b2bb-6cf049ee52be Order Id: 4ec03f80-6bce-11e1-b2bc-6cf049ee52be Order Id: 4ec03f80-6bce-11e1-b2bd-6cf049ee52be Order Id: 4ec03f80-6bce-11e1-b2be-6cf049ee52be Order Id: 4ec03f80-6bce-11e1-b2bf-6cf049ee52be Hettie Feest (4ec50240-6bce-11e1-b300-6cf049ee52be) Points: 82 HasGoldStatus: False MemberSince: 4/10/2009 12:00:00 AM (Utc) CreditRating: Neutral AverageRating: 0 Orders: Order Id: 4ec50240-6bce-11e1-b301-6cf049ee52be Order Id: 4ec50240-6bce-11e1-b302-6cf049ee52be Order Id: 4ec50240-6bce-11e1-b303-6cf049ee52be Press <ENTER> to exit...
NHibernate – 休眠查询语言
在本章中,我们将介绍 Hibernate 查询语言。HQL 在 Java 的 Hibernate 和 NHibernate 之间共享。
-
它与Criteria一起是最古老的查询机制。
-
它很早就实现了,它是一个基于字符串的查询API。
-
您可以通过ISession CreateQuery访问它,它几乎类似于 SQL。
-
它使用许多相同的关键字,但具有简化的语法。
-
它是最常见的示例之一,如果您正在寻找如何执行查询,您会经常找到 HQL 示例。
以下是 HQL 的一个简单示例 –
var customers = session.CreateQuery("select c from Customer c where c.FirstName = 'Laverne'");
-
所以在这里你可以看到他们从客户那里选择了C,它看起来很像SQL。就 NHibernate 而言,这是一个不透明的字符串,因此直到运行时您才知道这是否是有效的 HQL,这是缺点之一。
-
LINQ 提供程序的优势之一是您可以获得编译时支持。
-
但是HQL,是经常使用的最灵活的查询机制之一。据说,如果没有其他方法可以做到,那么有一种方法可以在 HQL 中做到。
让我们看一个简单的例子,在这个例子中我们将使用 HQL 重新创建我们的 LINQ 查询。您可以通过调用session.CreateQuery并使用 HQL 字符串作为参数传递来访问 HQL。
using System; using System.Data; using System.Linq; using System.Reflection; using HibernatingRhinos.Profiler.Appender.NHibernate; using NHibernate.Cfg; using NHibernate.Criterion; using NHibernate.Dialect; using NHibernate.Driver; using NHibernate.Linq; namespace NHibernateDemo { internal class Program { private static void Main() { var cfg = ConfigureNHibernate(); var sessionFactory = cfg.BuildSessionFactory(); using(var session = sessionFactory.OpenSession()) using(var tx = session.BeginTransaction()) { var customers = session.CreateQuery("select c from Customer c where c.FirstName = 'Laverne'"); foreach (var customer in customers.List<Customer>()) { Console.WriteLine(customer); } tx.Commit(); } Console.WriteLine("Press <ENTER> to exit..."); Console.ReadLine(); } private static Configuration ConfigureNHibernate() { NHibernateProfiler.Initialize(); var cfg = new Configuration(); cfg.DataBaseIntegration(x => { x.ConnectionStringName = "default"; x.Driver<SqlClientDriver>(); x.Dialect<MsSql2008Dialect>(); x.IsolationLevel = IsolationLevel.RepeatableRead; x.Timeout = 10; x.BatchSize = 10; }); cfg.SessionFactory().GenerateStatistics(); cfg.AddAssembly(Assembly.GetExecutingAssembly()); return cfg; } } }
-
这个 HQL 字符串看起来很像 SQL,主要区别在于 FirstName 是属性名而不是列名。
-
因此,如果两者之间存在差异,则使用属性名称。同样的事情,它看起来像一个表名,但它实际上是我们从中选择的类的名称。
-
如果后端表被命名为客户,我们仍然会在我们的 HQL 查询中使用客户。
让我们运行此应用程序,您将看到以下输出。
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 检索所有名字以字母 H 开头的客户。
using System; using System.Data; using System.Linq; using System.Reflection; using HibernatingRhinos.Profiler.Appender.NHibernate; using NHibernate.Cfg; using NHibernate.Criterion; using NHibernate.Dialect; using NHibernate.Driver; using NHibernate.Linq; namespace NHibernateDemo { internal class Program { private static void Main() { var cfg = ConfigureNHibernate(); var sessionFactory = cfg.BuildSessionFactory(); using(var session = sessionFactory.OpenSession()) using(var tx = session.BeginTransaction()) { var customers = session.CreateQuery("select c from Customer c where c.FirstName like 'H%'"); foreach (var customer in customers.List<Customer>()) { Console.WriteLine(customer); } tx.Commit(); } Console.WriteLine("Press <ENTER> to exit..."); Console.ReadLine(); } private static Configuration ConfigureNHibernate() { NHibernateProfiler.Initialize(); var cfg = new Configuration(); cfg.DataBaseIntegration(x => { x.ConnectionStringName = "default"; x.Driver<SqlClientDriver>(); x.Dialect<MsSql2008Dialect>(); x.IsolationLevel = IsolationLevel.RepeatableRead; x.Timeout = 10; x.BatchSize = 10; }); cfg.SessionFactory().GenerateStatistics(); cfg.AddAssembly(Assembly.GetExecutingAssembly()); return cfg; } } }
让我们再次运行您的应用程序,您将看到此查询返回了名称以 H 开头的所有客户。
Herman Crooks (4ead3480-6bce-11e1-b15c-6cf049ee52be) Points: 74 HasGoldStatus: True MemberSince: 12/3/2010 12:00:00 AM (Utc) CreditRating: Neutral AverageRating: 0 Orders: Order Id: 4ead3480-6bce-11e1-b15d-6cf049ee52be Order Id: 4ead3480-6bce-11e1-b15e-6cf049ee52be Order Id: 4ead3480-6bce-11e1-b15f-6cf049ee52be Order Id: 4ead3480-6bce-11e1-b160-6cf049ee52be Order Id: 4ead3480-6bce-11e1-b161-6cf049ee52be Order Id: 4ead3480-6bce-11e1-b162-6cf049ee52be Order Id: 4ead3480-6bce-11e1-b163-6cf049ee52be Hudson Bins (4ec03f80-6bce-11e1-b2b7-6cf049ee52be) Points: 56 HasGoldStatus: False MemberSince: 10/20/2008 12:00:00 AM (Utc) CreditRating: Terrible AverageRating: 0 Orders: Order Id: 4ec03f80-6bce-11e1-b2b8-6cf049ee52be Order Id: 4ec03f80-6bce-11e1-b2b9-6cf049ee52be Order Id: 4ec03f80-6bce-11e1-b2ba-6cf049ee52be Order Id: 4ec03f80-6bce-11e1-b2bb-6cf049ee52be Order Id: 4ec03f80-6bce-11e1-b2bc-6cf049ee52be Order Id: 4ec03f80-6bce-11e1-b2bd-6cf049ee52be Order Id: 4ec03f80-6bce-11e1-b2be-6cf049ee52be Order Id: 4ec03f80-6bce-11e1-b2bf-6cf049ee52be Hettie Feest (4ec50240-6bce-11e1-b300-6cf049ee52be) Points: 82 HasGoldStatus: False MemberSince: 4/10/2009 12:00:00 AM (Utc) CreditRating: Neutral AverageRating: 0 Orders: Order Id: 4ec50240-6bce-11e1-b301-6cf049ee52be Order Id: 4ec50240-6bce-11e1-b302-6cf049ee52be Order Id: 4ec50240-6bce-11e1-b303-6cf049ee52be Press <ENTER> to exit...
我们可以做更复杂的事情,比如想要所有订单数量大于 9 的客户的订单。以下是相同的 HQL 查询。
var customers = session.CreateQuery("select c from Customer c where size(c.Orders) > 9"); foreach (var customer in customers.List<Customer>()) { Console.WriteLine(customer); }
我们还需要在此处指明我们需要一个大小或计数或长度。在 HQL 中,我们可以选择使用如上所示的特殊大小方法。
如果您愿意,另一种写法是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 – 条件查询
在本章中,我们将介绍标准查询机制。该NHibernate的查询通过标准API, 可让您通过在运行时操作条件的对象构建查询。
-
这种方法允许您在不直接操作字符串的情况下动态指定约束,但它不会失去 HQL 的太多灵活性或功能。
-
另一方面,用标准表示的查询通常比用 HQL 表示的查询可读性差。
-
经典的标准语法是一个基于对象的查询 API,如下面的程序所示。
var customers = session.CreateCriteria<Customer>().Add(Restrictions.Like("FirstName", "H%"));
-
如您所见,我们正在对客户进行会话创建条件,现在我们正在向该查询添加限制对象。
-
这对于用户可以选择某些选项而不是其他选项的查询页面很有用。
-
将查询构建为类似于查询结构的树状结构比在 HQL 或 LINQ 中更容易,在 HQL 或 LINQ 中,您可以在 WHERE 子句中使用 AND 或 OR。
-
使用这些标准对象添加额外的限制会更容易。
让我们看一个简单的例子,我们将创建一个查询并通过createCriteria访问标准 API ,然后添加一个限制,即名字以 H 开头。
using System; using System.Data; using System.Linq; using System.Reflection; using HibernatingRhinos.Profiler.Appender.NHibernate; using NHibernate.Cfg; using NHibernate.Criterion; using NHibernate.Dialect; using NHibernate.Driver; using NHibernate.Linq; namespace NHibernateDemo { internal class Program { private static void Main() { var cfg = ConfigureNHibernate(); var sessionFactory = cfg.BuildSessionFactory(); using(var session = sessionFactory.OpenSession()) using(var tx = session.BeginTransaction()) { var customers = session.CreateCriteria<Customer>() .Add(Restrictions.Like("FirstName", "H%")); foreach (var customer in customers.List<Customer>()) { Console.WriteLine(customer); } tx.Commit(); } Console.WriteLine("Press <ENTER> to exit..."); Console.ReadLine(); } private static Configuration ConfigureNHibernate() { NHibernateProfiler.Initialize(); var cfg = new Configuration(); cfg.DataBaseIntegration(x => { x.ConnectionStringName = "default"; x.Driver<SqlClientDriver>(); x.Dialect<MsSql2008Dialect>(); x.IsolationLevel = IsolationLevel.RepeatableRead; x.Timeout = 10; x.BatchSize = 10; }); cfg.SessionFactory().GenerateStatistics(); cfg.AddAssembly(Assembly.GetExecutingAssembly()); return cfg; } } }
编译并执行上述代码后,您将看到以下输出。
Herman Crooks (4ead3480-6bce-11e1-b15c-6cf049ee52be) Points: 74 HasGoldStatus: True MemberSince: 12/3/2010 12:00:00 AM (Utc) CreditRating: Neutral AverageRating: 0 Orders: Order Id: 4ead3480-6bce-11e1-b15d-6cf049ee52be Order Id: 4ead3480-6bce-11e1-b15e-6cf049ee52be Order Id: 4ead3480-6bce-11e1-b15f-6cf049ee52be Order Id: 4ead3480-6bce-11e1-b160-6cf049ee52be Order Id: 4ead3480-6bce-11e1-b161-6cf049ee52be Order Id: 4ead3480-6bce-11e1-b162-6cf049ee52be Order Id: 4ead3480-6bce-11e1-b163-6cf049ee52be Hudson Bins (4ec03f80-6bce-11e1-b2b7-6cf049ee52be) Points: 56 HasGoldStatus: False MemberSince: 10/20/2008 12:00:00 AM (Utc) CreditRating: Terrible AverageRating: 0 Orders: Order Id: 4ec03f80-6bce-11e1-b2b8-6cf049ee52be Order Id: 4ec03f80-6bce-11e1-b2b9-6cf049ee52be Order Id: 4ec03f80-6bce-11e1-b2ba-6cf049ee52be Order Id: 4ec03f80-6bce-11e1-b2bb-6cf049ee52be Order Id: 4ec03f80-6bce-11e1-b2bc-6cf049ee52be Order Id: 4ec03f80-6bce-11e1-b2bd-6cf049ee52be Order Id: 4ec03f80-6bce-11e1-b2be-6cf049ee52be Order Id: 4ec03f80-6bce-11e1-b2bf-6cf049ee52be Hettie Feest (4ec50240-6bce-11e1-b300-6cf049ee52be) Points: 82 HasGoldStatus: False MemberSince: 4/10/2009 12:00:00 AM (Utc) CreditRating: Neutral AverageRating: 0 Orders: Order Id: 4ec50240-6bce-11e1-b301-6cf049ee52be Order Id: 4ec50240-6bce-11e1-b302-6cf049ee52be Order Id: 4ec50240-6bce-11e1-b303-6cf049ee52be Press <ENTER> to exit…
让我们看另一个简单的例子,在这个例子中,我们将检索名字等于“Laverne”的客户
using System; using System.Data; using System.Linq; using System.Reflection; using HibernatingRhinos.Profiler.Appender.NHibernate; using NHibernate.Cfg; using NHibernate.Criterion; using NHibernate.Dialect; using NHibernate.Driver; using NHibernate.Linq; namespace NHibernateDemo { internal class Program { private static void Main() { var cfg = ConfigureNHibernate(); var sessionFactory = cfg.BuildSessionFactory(); using(var session = sessionFactory.OpenSession()) using(var tx = session.BeginTransaction()) { var customers = session.CreateCriteria<Customer>() .Add(Restrictions.Eq("FirstName", "Laverne")) .List<Customer>(); foreach (var customer in customers) { Console.WriteLine(customer); } tx.Commit(); } Console.WriteLine("Press <ENTER> to exit..."); Console.ReadLine(); } private static Configuration ConfigureNHibernate() { NHibernateProfiler.Initialize(); var cfg = new Configuration(); cfg.DataBaseIntegration(x => { x.ConnectionStringName = "default"; x.Driver<SqlClientDriver>(); x.Dialect<MsSql2008Dialect>(); x.IsolationLevel = IsolationLevel.RepeatableRead; x.Timeout = 10; x.BatchSize = 10; }); cfg.SessionFactory().GenerateStatistics(); cfg.AddAssembly(Assembly.GetExecutingAssembly()); return cfg; } } }
让我们再次运行此应用程序,您将看到以下输出。
Laverne Hegmann (4e97c816-6bce-11e1-b095-6cf049ee52be) Points: 74 HasGoldStatus: True MemberSince: 4/4/2009 12:00:00 AM (Utc) CreditRating: Neutral AverageRating: 0 Orders: Order Id: 4ea14d96-6bce-11e1-b095-6cf049ee52be Order Id: 4ea14d96-6bce-11e1-b096-6cf049ee52be Order Id: 4ea14d96-6bce-11e1-b097-6cf049ee52be Order Id: 4ea14d96-6bce-11e1-b098-6cf049ee52be Press <ENTER> to exit...
现在,标准 API 的主要缺点之一是属性名称中的这些不透明字符串。因此,如果名字被重构为其他名称,重构工具不一定会选择不透明的字符串。
NHibernate – QueryOver 查询
在本章中,我们将介绍 QueryOver 查询。它是一种新语法,更像是使用方法链语法的 LINQ,如下面的查询所示。
var customers = session.QueryOver<Customer>() .Where(x => x.FirstName == "Laverne");
-
它仍然是隐藏的标准,但现在我们的查询是强类型的。
-
正如我们在标准查询中看到的,名字只是一个不透明的字符串,现在我们实际上使用的是x.FirstName,所以名字被重构和重命名,在链接样式标准查询中使用查询 over .
-
我们仍然可以做很多类似的事情,但是你不能在 query over 中使用 query comprehension 语法,你必须使用方法链语法,你不能混合和匹配链接和条件。
-
对于很多查询,通过 API 的查询非常有用,并且比直接使用 Criteria 提供了更容易理解的对象语法。
让我们看一个简单的例子,在这个例子中,我们将使用查询来检索名字是 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方法是什么,因此您将收到RunTime 异常。
异常表示无法识别的方法调用。在这里,我们正在做显而易见的事情,但它不一定有效。
让我们尝试其他的东西,比如 FirstName 等于“A%”,如下面的代码所示。
var customers = session.QueryOver<Customer>() .Where(x => x.FirstName == "A%"); foreach (var customer in customers.List()) { Console.WriteLine(customer); }
让我们再次运行它,您将看到我们不会得到如下所示的任何结果。
Press <ENTER> to exit...
要理解为什么我们没有得到任何结果,让我们看看 NHibernate 分析器。
如您所见,名字等于 A% 而不是。A% 在 SQL 中与 like 运算符一起使用。现在我们需要在 WHERE 子句中创建一个限制,如下面的程序所示。
var customers = session.QueryOver<Customer>() .Where(Restrictions.On<Customer>(c => c.FirstName).IsLike("A%")); foreach (var customer in customers.List()) { Console.WriteLine(customer); }
让我们再次运行您的应用程序,您将看到检索到的所有客户的名字都以 A 开头。
Alejandrin Will (4ea3aef6-6bce-11e1-b0b4-6cf049ee52be) Points: 24 HasGoldStatus: False MemberSince: 10/1/2011 12:00:00 AM (Utc) CreditRating: VeryVeryGood AverageRating: 0 Orders: Order Id: 4ea3aef6-6bce-11e1-b0b5-6cf049ee52be Austyn Nolan (4ea871b6-6bce-11e1-b110-6cf049ee52be) Points: 67 HasGoldStatus: True MemberSince: 12/29/2007 12:00:00 AM (Utc) CreditRating: Neutral AverageRating: 0 Orders: Order Id: 4ea871b6-6bce-11e1-b111-6cf049ee52be Antonia Murphy (4ea871b6-6bce-11e1-b121-6cf049ee52be) Points: 72 HasGoldStatus: True MemberSince: 6/15/2009 12:00:00 AM (Utc) CreditRating: Terrible AverageRating: 0 Orders: Order Id: 4ea871b6-6bce-11e1-b122-6cf049ee52be Order Id: 4ea871b6-6bce-11e1-b123-6cf049ee52be Order Id: 4ea871b6-6bce-11e1-b124-6cf049ee52be Order Id: 4ea871b6-6bce-11e1-b125-6cf049ee52be Order Id: 4ea871b6-6bce-11e1-b126-6cf049ee52be Order Id: 4ea871b6-6bce-11e1-b127-6cf049ee52be Order Id: 4ea871b6-6bce-11e1-b128-6cf049ee52be Order Id: 4ea871b6-6bce-11e1-b129-6cf049ee52be Order Id: 4ea871b6-6bce-11e1-b12a-6cf049ee52be
除了使用这种新的QueryOver语法外,它的工作方式与以前相同。许多开发人员发现 LINQ 语法更平易近人,而且经常做正确的事情。
如果 LINQ 无法处理,那么您将开始查看 HQL 或 Criteria,看看它们是否更合适。
它只是为您提供了不同的语法,因此 Criteria、创建条件和 QueryOver 都为您提供了另一种查询机制,允许您使用 NHibernate 从数据库中提取数据。
NHibernate – 原生 Sql
在本章中,我们将介绍如何在 NHibernate 中使用原生 SQL 查询。如果您已经使用手写 SQL 多年,您可能会担心 ORM 会剥夺您习惯的一些表达能力和灵活性。
-
NHibernate 强大的查询工具使您几乎可以在 SQL 中执行任何操作,在某些情况下还可以执行更多操作。
-
对于您无法使 NHibernate 自己的查询工具完全按照您的要求执行的极少数情况。
-
NHibernate 允许您使用数据库的本机 SQL 方言检索对象。
让我们看一个 NHibernate 中原生 SQL 查询的简单示例。
using System; using System.Data; using System.Linq; using System.Reflection; using HibernatingRhinos.Profiler.Appender.NHibernate; using NHibernate.Cfg; using NHibernate.Criterion; using NHibernate.Dialect; using NHibernate.Driver; using NHibernate.Linq; using NHibernate; namespace NHibernateDemo { internal class Program { private static void Main() { var cfg = ConfigureNHibernate(); var sessionFactory = cfg.BuildSessionFactory(); using(var session = sessionFactory.OpenSession()) using(var tx = session.BeginTransaction()) { IQuery sqlQuery = session.CreateSQLQuery("SELECT * FROM CUSTOMER").AddEntity(typeof(Customer)); var customers = sqlQuery.List<Customer>(); foreach (var customer in customers) { Console.WriteLine(customer); } tx.Commit(); } Console.WriteLine("Press <ENTER> to exit..."); Console.ReadLine(); } private static Configuration ConfigureNHibernate() { NHibernateProfiler.Initialize(); var cfg = new Configuration(); cfg.DataBaseIntegration(x => { x.ConnectionStringName = "default"; x.Driver<SqlClientDriver>(); x.Dialect<MsSql2008Dialect>(); x.IsolationLevel = IsolationLevel.RepeatableRead; x.Timeout = 10; x.BatchSize = 10; }); cfg.SessionFactory().GenerateStatistics(); cfg.AddAssembly(Assembly.GetExecutingAssembly()); return cfg; } } }
上面的示例使用CreateSQLQuery()来获取对象列表,您还会注意到您希望查询返回的根实体类型被指定为 Customer。
让我们运行您的应用程序,您将看到所有客户都从数据库中检索到。
Emerson Prosacco (4ec2a0e0-6bce-11e1-b2cf-6cf049ee52be) Points: 17 HasGoldStatus: False MemberSince: 6/22/2007 12:00:00 AM (Utc) CreditRating: Excellent AverageRating: 0 Orders: Order Id: 4ec2a0e0-6bce-11e1-b2d0-6cf049ee52be Order Id: 4ec2a0e0-6bce-11e1-b2d1-6cf049ee52be Order Id: 4ec2a0e0-6bce-11e1-b2d2-6cf049ee52be Order Id: 4ec2a0e0-6bce-11e1-b2d3-6cf049ee52be Order Id: 4ec2a0e0-6bce-11e1-b2d4-6cf049ee52be Kaci Friesen (4ec2a0e0-6bce-11e1-b2d5-6cf049ee52be) Points: 30 HasGoldStatus: True MemberSince: 5/25/2007 12:00:00 AM (Utc) CreditRating: VeryVeryGood AverageRating: 0 Orders: Order Id: 4ec2a0e0-6bce-11e1-b2d6-6cf049ee52be Order Id: 4ec2a0e0-6bce-11e1-b2d7-6cf049ee52be Order Id: 4ec2a0e0-6bce-11e1-b2d8-6cf049ee52be Order Id: 4ec2a0e0-6bce-11e1-b2d9-6cf049ee52be Order Id: 4ec2a0e0-6bce-11e1-b2da-6cf049ee52be Order Id: 4ec2a0e0-6bce-11e1-b2db-6cf049ee52be Eveline Waters (4ec2a0e0-6bce-11e1-b2dc-6cf049ee52be) Points: 58 HasGoldStatus: False MemberSince: 10/29/2009 12:00:00 AM (Utc) CreditRating: Good AverageRating: 0 Orders: Order Id: 4ec2a0e0-6bce-11e1-b2dd-6cf049ee52be Order Id: 4ec2a0e0-6bce-11e1-b2de-6cf049ee52be Order Id: 4ec2a0e0-6bce-11e1-b2df-6cf049ee52be Order Id: 4ec2a0e0-6bce-11e1-b2e0-6cf049ee52be Order Id: 4ec2a0e0-6bce-11e1-b2e2-6cf049ee52be Molly Kuhn (4ec2a0e0-6bce-11e1-b2e3-6cf049ee52be) Points: 73 HasGoldStatus: False MemberSince: 12/16/2007 12:00:00 AM (Utc) CreditRating: VeryGood AverageRating: 0 Orders: Order Id: 4ec2a0e0-6bce-11e1-b2e4-6cf049ee52be Order Id: 4ec2a0e0-6bce-11e1-b2e5-6cf049ee52be Order Id: 4ec2a0e0-6bce-11e1-b2e6-6cf049ee52be Order Id: 4ec2a0e0-6bce-11e1-b2e7-6cf049ee52be Order Id: 4ec2a0e0-6bce-11e1-b2e8-6cf049ee52be Order Id: 4ec2a0e0-6bce-11e1-b2e9-6cf049ee52be Order Id: 4ec2a0e0-6bce-11e1-b2ea-6cf049ee52be Order Id: 4ec2a0e0-6bce-11e1-b2eb-6cf049ee52be Order Id: 4ec2a0e0-6bce-11e1-b2ec-6cf049ee52be
这是另一种编写本机 SQL 查询的方法,如下所示。
IList<Customer> customers = session.CreateSQLQuery("SELECT * FROM CUSTOMER") .AddScalar("Id", NHibernateUtil.Guid) .AddScalar("FirstName", NHibernateUtil.String) .AddScalar("LastName", NHibernateUtil.String) .List<Customer>();
-
如您所见,上述查询指定了 SQL 查询字符串以及要返回的列和类型。
-
这将返回一个 IList 的 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 – 流畅的休眠
在本章中,我们将介绍流畅的 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; } } }
现在我们需要使用流畅的 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 个客户。