第一章 NHibernate介绍

在这一章中,我们将简要介绍NHibernate。本章主要针对NHibernate或ORM的初学者。因此,我们将从讨论什么是ORM以及它能解决什么类型的问题开始。然后我们会深入到NHibernate,并讨论NHibernate提供的具体服务。接下来,我们将讨论NHibernate的最新版本,以及最新版本中添加了哪些新特性。当我向你介绍NHibernate的时候,我也会触及NHibernate的重要组成部分。最后,我将尝试解答:一些专家称使用ORM是一个坏主意。

 

对于那些使用过或读过Entity Framework实体框架(EF)的人,我在这里增加了一节内容,讨论当前版本的EF中缺少的NHibernate特性。这并不是说NHibernate比EF更好。相反,这些信息将帮助您为工作选择合适的工具。

 

1.何为ORM

ORM即Object Relational Mapping对象关系映射这种技术允许您将对象映射到关系数据库,反之亦然。这里,术语“对象”主要指类的实例。使用ORM技术的工具也称为ORM,它代表对象关系映射器。NHibernate就是这样一个工具。让我们看一个非常简单的例子,以理解这种技术到底是做什么的。假设您有以下Customer类:

1 public class Customer
2 {
3     public string FirstName {get; set;}
4     public string LastName {get; set;}
5     public string EmailAddress {get; set;}
6     public int Age {get; set;}
7 }

下载示例代码

您可以从您的帐户http://www.packtpub.com下载您购买的所有Packt出版书籍的示例代码文件。如果您在其他地方购买了这本书,您可以访问http://www.packtpub.com/support并注册,直接将文件通过电子邮件发送给您。
 
每个customer对象都将由内存中上述类的一个实例表示。每个这样的实例也将持久化到数据库中。假设下面的数据库表用于存储customer实例:
1 CREATE TABLE tblCustomer
2 (
3     FirstName NVARCHAR(100),
4     LastName NVARCHAR(100),
5     EmailAddress NVARCHAR(100),
6     Age INT
7 );

上面的语法适用于MS SQL Server数据库。如果你使用不同的RDBMS关系数据库,那么语法可能不同。

根据使用的ORM,您将有一种方法告诉ORM, Customer类对应于数据库中的tblCustomer表。类似地,您可以看出FirstName、LastName、EmailAddress和Age属性映射到tblCustomer表中具有相同名称的列。一旦所有这些就绪,您就可以告诉ORM保存Customer类的一个实例;它将确保在tblCustomer表中插入一条记录,该记录具有所有列的适当值

 
简而言之,这就是ORM。ORM将为您处理所有数据库CRUD操作,而无需编写一行SQL脚本。但是,要理解ORM的最重要目标是,它试图在OO世界和关系世界之间架起一座桥梁。使用面向对象原则编写的程序遵循一组规则并支持一组特定的数据类型。另一方面,大多数RDBMS关系数据库遵循从集合理论派生的规则,并支持一组数据类型,这些数据类型可能并不都与相应的数据类型兼容。除此之外,在如何构造新对象、它们如何相互关联、允许对它们进行何种操作等方面也存在差异。
 
从OO程序的角度来看,所有这些差异都令使用数据库变得困难。这些差异也称为阻抗失配,这个术语来自电气工程。阻抗是测量电流流过电路的难易程度的一种方法。为了使两个电路在连接时工作一致,它们的阻抗应该匹配。同样地,为了让一个OOP程序与RDBMS合拍,它们之间的阻抗就必须匹配。ORM就是来做这个工作的。
 
 
2.NHibernate是什么?
显然,它是ORM。如果我可以引用NHibernate网站官宣:
“NHibernate是一个成熟的,开源的.net框架的对象关系映射器。它开发活跃、功能完整,并在数千个成功的项目中得到应用。”
 
NHibernate是Java社区一个非常著名的ORM——Hibernate的.NET移植版。现在,Hibernate已经相当古老了,被数以千计的生产应用程序使用。NHibernate不仅继承了大部分好的特性而且在.net中功能更加丰富了,比如LINQ查询。为了理解它的强大功能,让我们举一个非常简单的例子。如果你有上一节的Customer类,映射到一个叫做tblCustomer的数据库表,你想要获取所有28到35岁的客户,若用NHibernate,你可以这么写:
 
1 var customers = from c in session.Query<Customer>()
2                 where c.Age > 28 && c.Age < 35
3                 select c;

这是一个LINQ查询。LINQ即 Language-Integrated Query语言集成查询。这是C#和Visual Basic中引入的特性,提供了类似于SQL的数据查询和更新语法。LINQ在C#语言的限制下可以针对多种数据源工作;而且,它与IQueryable类型工作得很好。如果你不熟悉LINQ,也不要太担心,因为我们将在接下来的章节中介绍它们。您一定想知道session.Query<Customer>()是什么?这是一个NHibernate API,我们将在本书中广泛使用。现在,session.Query<Customer>()返回IQueryable <Customer>对象。正是这个queryable可查询类型,使编写之前的LINQ查询语句成为可能。

然后NHibernate处理前面的LINQ查询并生成如下SQL:
1 SELECT a_.FirstName, 
2     a_.LastName,
3     a_.EmailAddress,
4     a_.Age
5 FROM tblCustomer a_
6 WHERE a_.Age > 28 AND a_.Age < 35

正如所见,您所编写的只是针对C#类的一个简单的C# LINQ表达式。不必考虑数据库表名是什么,以及如何形成正确的SQL查询;NHibernate已经为你处理了所有这些。NHibernate有很多这样的特性,让编写数据库交互代码变得轻而易举。我们将在本书中介绍NHibernate的这些特性。

 
NHibernate是一个完全依靠社区贡献而繁荣的开源项目。和任何开源项目一样,这给NHibernate的每个方面都提供了很多透明性。你可以通过https://github.com/nhibernate/nhibernate-core/从GitHub下载源代码,查看它们是如何实现的。您可以在他们的官方JIRA上提出bug,参与关于缺陷的正确修复、新特性的开发和未来路线图的讨论。所有这些活动大多是由一些顶级贡献者领导的,但是透明和社区包容是NHibernate继续前进的动力。你可以通过导航到http:// nhibernate.jira.com来访问NHibernate 的JIRA板块。我不会在这里花太多的篇幅来讨论NHibernate。除了整本书涵盖了NHibernate的所有重要方面之外,我现在将在这一章的其余部分窥视一些有趣的NHibernate特性。
 
在我进入下一节之前,我想提一下,在我写这本书的时候,NHibernate的最新稳定版本是4.0.3.400。请访问NHibernate的NuGet页面https://www.nuget.org/packages/NHibernate/确认NHibernate的最新版本。最新版本是一个重要的里程碑,因为这是一个主要的发行版,大约比上一个主要的3.0发行版晚了3年。在这3年里有几个小版本。所有这些版本都在版本4.0中添加了很多很棒的特性。让我们简单介绍一下这些特性。
 
3.NHibernate 4.0有什么新功能?
NHibernate 4.0.3.400的通用版本发布于2014年8月17日. 这是在撰写本书时NHibernate的最新版本。如果你是ORM领域的新手,或者以前从未使用过NHibernate,那么你可能不会觉得这个部分很有趣。但是,嘿,为什么不继续读下去,你可能会找到一些与你共鸣的东西:
 
  • NH 4.0是基于.net 4.0构建的。这对的用户可能没有太大影响。因为你仍然可以在.net 4.0上使用旧版本的NHibernate。然而,这对NHibernate来说是一个重大的转变,因为它让NHibernate开发者可以利用.net4.0 的新特性。一个直接的结果是NHibernate 4.0的Set集合现在不再使用Iesi.Collection库的Iesi.Collections.Generic.ISet<T> 。因为.net 4.0开始内建了ISet<T>和实现类(如HashSet<T>)。
  • 通过实现本地IIF函数、查询分页和SQL Server 2012 sequences ,改进了对SQL Server 2012的支持。
  • “代码映射”使您不必编写和维护繁琐的XML映射文件。但是,一般人都不喜欢这个特性,因为它直到最近才开始被用的多起来。NHibernate 4.0在这方面有一些缺陷,因此通过代码来进行映射是一种改进。
  • 我们都喜欢LINQ,我们也都讨厌NH的LINQ支持从来没有符合过Criteria或QueryOver。社区在这个领域做出了巨大的贡献,已经解决了140多个问题。NHibernate 4.0的LINQ支持,在很多方面都与其他查询providers提供程序相一致。我们将在本书后面的章节中大量使用这个特性。
 
4.NHibernate之对于EF用户
如果你正在使用或曾经使用过Entity Framework 实体框架(EF),那么你可能知道NHibernate。虽然EF已经运营多年了,但也只是在EF 6性能做出较大提升,以及实现了ORM的一些核心功能之后,最近几年才得到一些关注。尽管EF最近很受欢迎,一个很自然的问题是你为什么要看NHibernate?对于初学者来说,NHibernate可能是现存最古老的ORM了。多年来,NHibernate的代码库已经在成千上万的生产应用程序中使用,而且非常坚固。然而,这听起来是非常主观的,所以让我给你列一个在NHibernate中有而EF中没有的功能列表。
 
  • NHibernate对二级缓存有稳定而复杂的支持。NHibernate的缓存实现被多个缓存提供者支持,范围从内存哈希表到分布式缓存解决方案,比如NCache。
  • NHibernate支持映射字典。字典映射在各种不同的情况下都很有用,但我想到的一个有趣的场景是构建多租户系统。在多租户系统中,应用程序的同一个实例为多个租户提供服务,它们的数据存储在单个数据库实例中。如果某个特定租户需要在某个实体上具有其他属性来处理关于该实体的其他数据,那么可以将这些额外数据存储在键值对字典中。这个字典被声明为实体的属性。Hibernate可以将这些字典直接映射到合适的表模式中。
  • 在需要更新的情况下大量的数据库记录作为批处理作业的一部分,NHibernate的一般会话对象可能过于缓慢,跟踪每一个变化的实体和执行所有的数据库关系/约束之前更改保存到数据库。然而,NHibernate的无状态会话大大加快了速度,代价是失去了一些特性,比如变化跟踪和内存中数据库约束的执行。EF也有一些支持这样的行为,通过扩展方法称为AsNoTracking或通过DbContextConfiguration类的专门的设置。然而,在我看来,这两个选项都不如NHibernate的无状态会话那么直观。
  • 总的来说,NH比EF支持更多的数据库。EF构建、测试和最常用的是MS SQL Server,也多用于Oracle。但是微软对其他数据库(如MySQL、Sybase、ASE、PostgreSQL和DB2)的支持程度就不同了,根本不能与前两者相提并论。如果你使用后面这些数据库,那么如果你使用NHibernate,你面临的问题会更少。
  • 标识生成是ORM提供的重要特性之一。每个数据库表都必须有一个标识列,用于惟一标识特定记录。为了简单起见,我们在介绍部分跳过了这一部分。与数据库表中的identity标识列对应,您将拥有一个保存identity值的属性。然后可以为这个类的每个新实例生成一个惟一的标识值,也可以让ORM为您生成一个。如果使用得当,标识生成可以节省一些数据库访问并提高性能。特定的标识生成策略可能不适用于所有情况,因此拥有多个策略总是有帮助的。NHibernate捆绑了一些身份生成策略,而EF仍在追赶中。
  • NHibernate对并发管理的支持要好得多。并发性是两个会话(通常表示两个连接到数据库的不同终端用户)试图更新相同的数据库记录。如果并发处理不好,会导致最后一个更新记录的会话处理数据的陈旧副本。如果数据库中显示的数据比内存中加载的数据有最新的更新版本,则称为过时数据。在这种情况下,如果没有进行适当的检查,处理陈旧数据的会话可能最终用一些旧值覆盖合法记录。NHibernate至少有三种不同的并发管理模式,其中NHibernate检测过时数据的更新,中止更新,并让应用程序知道。我们将在接下来的章节中详细介绍这些并发模型,但值得一提的是EF对并发的支持有限,它只有在与MS SQL Server数据库一起使用时才能最有效地工作。
  • NHibernate支持全局flters。全局flters允许您对动态SQL flters进行一次提取,并将它们应用于每个数据库查询,或者选择性地将它们应用于某些数据库查询。在使用多租户数据库场景时,这个特性非常有用。我能想到的另一种场景是当你在数据库中存储字符串的本地化版本时你通过使用区域性flter根据当前线程区域性选出正确的版本。
  • NHibernate支持四种不同的数据查询机制。而LINQ是最简单的(NHibernate和EF都支持这个),其他三个即HQL, Criteria和QueryOver,只有NHibernate才支持。使用这些工具可以做的一件有用的事情是调优。使用这些不同的查询机制,可以更好地控制生成的fnal SQL。大多数DBA需要这种功能。
5.使用ORM是个昏招吗?
ORM在使用关系数据库时提供了酸爽的开发体验。尽管如此,一些业内人士仍然认为使用ORM不是一个好主意。在所有反对使用ORM的理由中,最常见的两个是:
  • ORM抽象了数据库,因此,任何开发人员都必须掌握的重要数据库知识通常被忽略了。
  • 使用ORM会导致应用程序的性能下降。
深入研究这些原因将会很有趣。确实ORM抽象了数据库,并以透明的方式为您处理几乎所有的数据库交互。您不需要深入理解ORM如何从数据库中获取数据。如果您掌握了ORM提供的API,就能很好地通过最常用的查询与中等复杂度的数据库交互。此外,由于这几乎总是有效的,你永远不需要知道ORM在为你做什么。因此,您确实错过了学习如何使用数据库的机会。如果你是使用ORM生成数据库结构(比如code first),那么可能会错过重要的数据库结构,如键、关联、关系和索引。所以,第一个推论很有道理。如果您确实属于这类开发人员,那么数据库知识的缺乏将在最意想不到的时候给您带来最大的打击。
 
谈到ORMs导致的性能下降,我只能说这有一定的道理,但这是非常主观的。我之所以说主观,是因为“表现”这个词本身是站不住脚的。当我们谈到性能时,我们通常指的是基准测试。这个基准测试可以是一个系统的预期性能,也可以是用于比较的类似系统的性能。ORM的性能参数通常是在与其他与数据库交互的方法进行比较的情况下得出的,这些方法包括但不限于通过ADO.NET使用纯SQL或使用微型ORM。虽然微型orm确实提供了最好的性能,与ADO.NET稍有不同。ADO.NET提供了骨架,您需要编写从DataSet/DataReader读取数据和填充到对象的大部分代码。依赖于您构建高性能系统的经验,您能从ADO.NET中挤出的性能,比ORM提供的可能更多。然而,前提是“有建立高性能系统的经验。”如果您没有这样的经验,那么最好坚持使用ORM,至少不会在使用数据库时犯常见错误。
 
 
6.为什么ORM是更好的选择?
那么,我们为什么要使用ORM呢?让我们回过头来考虑一下不使用ORM时的选择。在缺少ORM的情况下,您可以使用micro-ORM,也可以将自己的数据访问层放在一起,使用ADO.NET之类的东西实现。micro-orm对对象关系映射采用极简方法。其中一些使用了高级语言特性和约定来自动将类映射到数据库结构,而另一些则需要自己显式写SQL语句(后者顶多算个SQLHelper)。
 
Micro-ORM可以算是orm的小弟,是一个完全不同的主题,所以为了讨论的目的,我不打算考虑Micro-ORM。因此,我们只需要构建自己的数据访问层。这是什么意思呢?首先,也最重要的是,这意味着您将花费大量的时间来构建一些让您与关系数据存储交互的东西。而且,您将与来自“No-ORM”阵营的许多其他开发人员一起完成这项工作。你能想象这个轮子要被重新发明多少次吗?如果您在一个人人都重视“代码重用”的环境中工作,那么您可能会尝试在多个项目中重用该数据访问层,从而限制重复。然而,实际上,这种情况很少发生,原因有二。第一,大多数时候,我们都在忙于交付自己的东西,而忽略了其他团队的工作,忽略了你在项目中可能会用到的工作。第二个原因是,第一个构建原始数据访问层的团队是根据他们的需要定制的,而不是完全符合你自己的需要。所以,你决定自己造。所以,所有这一切导致无数的开发人员为他们开始工作的每一个新项目重新构建类似的东西。接下来的挑战是如何维护这样的数据访问层的质量。对于简单的应用程序,可以在单个类中编写完整的数据访问层,但这种方法通常不适用于复杂的应用程序。当来自数据存储的需求变得复杂时,实现能够处理所有需求的数据访问层就变得具有挑战性。如果没有适当的经验和不同经验模式的知识,您构建的一定会是一个质量无法保证的数据访问层。
 

每个数据访问层都遵循特定的模式与数据库交互。虽然有些数据访问模式经受住了时间的考验,但还有一些模式无论如何都应该避免。推出自主开发的数据访问层意味着知道哪些对别人有复用性,哪些不复用。这也意味着有一个直觉:哪些将为你的项目工作。这取决于项目未来如何发展。这些东西来之不易,通常擅长这些事情的人都有多年处理这些事情的经验。不可能每次都正好有这样的人负责搞数据访问层。所以,很可能事情会出问题,你会把时间搭在处理这些问题上。ORM尤其是NHibernate,已经存在多年了。在NHibernate中实现的数据访问模式被证明是最好的。同时,它与数据库交互时也会注意避免被认为有害的模式。作为一个开源项目,NHibernate欢迎公众的投稿和缺陷报告。所以,NHibernate随时间的推移更加丰富和稳定。公众的献力献策意味着NHibernate是通过从数百名开发人员的错误中学习成长的,和倾听了全世界广大开发者社区的需求。不使用NHibernate或任何成熟的开源ORM只会意味着从你的系统中分离出如此庞大的公共知识源而重造轮子。

 
7.数据访问层所需的非功能特性
除此之外,每个数据访问层迟早都需要提供一些非关键功能的需求。以下是这些特点的代表性清单:
  • 日志:日志可能是最重要的非功能需求之一。您不仅需要记录错误和异常,还需要记录重要信息,比如发送到数据库的SQL查询的详细信息以及从数据库返回的数据
  • 缓存:缓存听起来可能很容易实现,但是随着您的领域变得复杂,数据查询模式也会变得复杂。这些复杂的数据查询模式具有非常不同的缓存需求,实现这些缓存需求并不容易。
  • 连接管理:你可能认为ADO.NET提供连接管理和连接池。事实上,确实如此。但是我们还需要更多关于连接管理的特性。在实际应用程序中,您需要能够跨多个查询重用连接,能够建立新的连接,能够在正确的时间断开现有连接,等等。
  • 事务管理:与连接管理一样,ADO.NET确实提供了基本的事务管理,但围绕这一领域的大多数实际软件的需求很快就会变得复杂起来。
  • 并发管理:多个用户对同一数据的并发更新可能会导致难以发现的缺陷。能够知道您是否在处理陈旧的数据对于构建健壮的应用程序至关重要。
  • 延迟加载:延迟加载是指只加载当前需要的对象的一部分。我们将在后面的章节中更详细地介绍延迟加载。现在,值得注意的是,延迟加载并不是一个容易实现的特性。
  • 安全性:数据访问层是存储在数据库中的关键数据的主要网关。该数据的安全性是非常重要的,因此,任何数据访问层都需要得到保护,并且应该对SQL注入攻击进行增强。
  • 批处理查询:一些查询可以与其他查询一起批处理,稍后执行。这提高了SQL连接的吞吐量,从而提高了性能。并不是所有的数据访问层都能够在以后批量处理和执行查询。
您自己写的数据访问层不太可能拥有所有这些特性。您可能会设法构建其中的一些特性,但是要将所有的特性都包含进来实际上是不可能的。这主要是因为项目的主要目标是交付业务功能,而不是构建功能完整的数据访问层。即使你成功地将具备以上大部分特性的数据访问层整合在一起,你测试每种边界情况并消除大多数缺陷的几率有多大?
 
另一方面,任何成熟的ORM都会提供这些特性中的大部分。因为这本书是关于NHibernate的,所以我将在这里集中介绍NHibernate。NHibernate从一开始就内置了所有这些特性。这些特性不仅经过了及时的测试,而且已经根据开发人员的实际需要形成了。由于它是一个开源项目,大量的社区反馈和贡献已经形成了这些特性。因此,您肯定会看到这些特性的高级实现。
 
鉴于上述所有优点,我还会认为使用ORM是昏招吗?我认为不是。相反,我认为使用ORM是个好主意。
 
 
8.NHibernate的关键组成
现在我们知道使用ORM从本质上来说不是一个坏主意,实际上是一种非常有效的方式来编写与数据库交互的代码,让我们把注意力转回NHibernate。
NHibernate建立在三个重要的基础之上。理解这些关键组成对于有效地理解和使用NHibernate是至关重要的。我们将在接下来的章节中详细探讨这些关键组成,但现在我想给你一个简短的介绍。
 
8.1映射
NHibernate使用映射来找出哪个类映射到哪个数据库表类的哪个属性映射到表的哪个列每当你的应用程序尝试与数据库交互时,NHibernate就会引用映射来生成合适的SQL语句发送到数据库执行。
 
有三种不同的方法可以告诉NHibernate映射。其中一种方法基于传统的XML,而另外两种方法基于代码并提供连贯的API来表示映射。在第3章,我会详细地介绍这些方法。然而,我们将在其他章节中只使用代码API来设置映射,也称为代码映射
 
为了便于介绍,我简化了映射,但是映射提供了许多高级特性,超出了映射表和列等基础功能。这些高级特性会影响SQL的生成、应用程序的性能和数据访问层的弹性。因此,为了更有效地使用NHibernate,理解和掌握映射是很重要的。
 
8.2配置
NHibernate允许你在运行时指定很多细节。这些细节会影响NHibernate做什么以及NHibernate如何做特定的事情。虽然配置是一件小事,但掌握它却很重要。在大多数项目中,你会在最初阶段处理配置,一旦一切都解决了,你就很少会再回头动到它。只有当你需要打开或关闭某些东西时,你才会回头找它,知道如何去配置,这样做会节省你的时间。NHibernate confguration让你可以控制以下功能:
 
  • 您希望连接到哪个数据库(MS SQL Server、Oracle等)
  • 数据库在哪里(读取连接字符串)
  • 使用哪个SQL驱动程序(或者NHibernate应该使用哪个SQL客户端库)
  • 类映射在哪里
  • NHibernate应该记录重要log信息吗
  • NHibernate是否应该默认缓存实体对象
  • NHibernate应该如何管理会话
  • NHibernate应该使用二级缓存吗
  • 二级缓存在哪里
如您所见,这里有一些有用的配置选项。我们将在<第4章NHibernate热机> 中看到这些选项的细节以及如何使用它们。
Confguration不仅仅是提供一些NHibernate设置的默认值。配置可以载入内存,你可以用它做一些神奇的事情。其中一种常见的做法是使用confguration来生成数据库脚本。一旦你定义了映射并创建好配置,就可以生成一个SQL脚本来构建所有数据库结构、表、关系、约束、索引等等。我们将在<第4章NHibernate热机> 中详细介绍。现在,NHibernate配置是多么的重要和强大是值得记住的。
 
 
8.3会话
Session是NHibernate中最常用的对象。任何你的应用程序需要使用NHibernate做的数据库交互,必须使用一个会话对象来做可以将会话对象看作数据库连接connection的封装。会话对象不仅管理到数据库的连接,而且还公开方法(有些是直接的,有些是通过扩展方法间接的),这些方法允许您编写代码CRUD数据库。会话对象还公开用于事务管理的API。此外,会话对象实现了一种提高查询性能的缓存形式。这个列表可以继续下去,因为会话对象提供了很多特性。但是,让我们把这些特性留给后面的章节,在那里我们将详细地讨论它们。
 
由于会话对象提供了丰富的特性,会话对象是最常用的NHibernate类型。NHibernate实现它的方式是,创建一个新的会话对象是一个非常廉价的操作(消耗小)。但是,这并不意味着您应该自由地创建会话对象的新实例。当您随机创建大量会话对象时,您将不得不直接或间接地付出一些代价。如果你遵守一些规则,你就能充分享受廉价会话对象带来的好处。我们将在本书的不同章节适当的时候涵盖所有这些规则。
 
 
9.本章小结
大多数人选择跳过任何一本书的导言章节。如果你没跳过本章,你看完我向你介绍的整个内容,特别是ORMs和NHibernate,那么恭喜你,你已经有了一个很好的开始。现在你大概知道了NHibernate是什么,它为你解决了什么问题。你可能还对NHibernate的一些高级特性有所了解。最后,我希望您现在对“使用ORM是个昏招吗?”这样的问题,给一个响亮的回答:“不是昏招”。
 
 

posted on 2020-07-10 17:00  困兽斗  阅读(1291)  评论(0编辑  收藏  举报

导航