技术开发 频道

ASP.NET中NHibernate的非常好的实践


1.介绍

   为什么使用ORM?

    作者提示:下面是我在业余时间从一本书上摘抄的摘录。

    当下一个十年,我们不会看到银弹。无论在技术上还是管理技巧上,都没有单一的发展。无论在生产力、可靠性还是简易性方面,在十年中,这些技术和技巧都会取得很大的数量级的进步。

    在1986的No Silver Bullet这句圣言现在被Frederick Brooks'放入他的著作中,Brook’s的说一直都是对的,直到大概10年后,即1997,在软件世界中喻为银弹的NeXT's Enterprise Object Framework,世界靠前个对象-关系影射(ORM)框架发布了.虽然不成熟并经常被认为是触犯了软件教条的异教徒,但是仍被很多人普遍接受的ORM技术。事实上,当它被正确使用,它就是软件开发的银弹.随着NHibernate的成熟,ORM中的银弹已经正式地被介绍给.net世界。

    ORM技术最经常的反对者,大体上来说,是使用微软技术的开发者。(因为在过去得十多年里,我一直在研究这个领域,我很高兴能先提高我们)。“如果微软没有开发它,那么就等微软找到正确地方法来开发它”这好像是一条没有成文的规则。在即将到来得C# 3.0中,微软打算使用"LINQ to Entities"来完成它。(是的,正式抛弃使用"LINQ to Entities")开发者可能继续等待这种技术,或者,有另外一个选择,开始迅速意识到ORM的好处。公平地说,虽然微软没有能开发出来的东西很少,但是自从.NET. Circa 2004面世后,这些没有开发出来东西一直在增多,微软人员(像是母亲)没能创建出来的开放资源工具终于开始达到成熟,这种成熟让人高兴,每一个致力于.NET的人员都要仔细考虑这种成熟。

    这些技术的其它反对者暗示说ORM扼杀了应用程序性能,仅仅在最初的发展阶段给开发带来了巨大的进步。而且,这种争论还在继续,当性能和可维护性的问题开始带来更加值得注意的影响时,使用ORM在项目后期会给项目成功带来严重的损害。三个明显反驳浮现脑海,在回应这些论点。

    首先,作为开发者,使用一个能够支持ORM的框架,例如:NHibernate会提高你的工作性能.你可以少于90%的时间(对,我说的就是少于90%的时间)来开发数据访问代码,然后可以将品质时间花在提高领域模型以及调整性能――假设有必要的话。而且,在综合利用一个简单的压型工具需要很长的时间才能暗示5%的代码,5%的代码会导致95%的性能瓶颈。当在数据访问层就是瓶颈的情况中,为了获得巨大的改善,通常可以作一些简单的调整。顺便提及一声,不用ORM也不会有多少不同。(一定要阅读Peter Weissbrod写的《introductory article to profiling NHibernate applications》)。当ORM构架成为瓶颈并且不能靠调整来改进的情况是很少的,如果适当地分离了数据访问层,就非常容易绕过ORM。

    第二,明确地说,在数据载入行为的所有方面,NHibernate不可思议地控制了它们。对于开发人员的生产力,应用程序灵活性以及应用程序稳定性来说,这一控制给它们产生了有利的影响。当然高速缓冲存储器是可以使用,同时在非ORM解决方案中高速缓冲存储器也是可以使用的。其它out-of-the-box特点,包括lazy loading,对继承的支持,不可变类声明,加载数据到只读属性,支持泛型、存储过程等待。这些强大的特性都在在实际项目中被验证是可行的,另外这些都能够在减少自定义数据访问层的改错和调试时间。(而且,如果你真正地觉得要进入代码,仅仅用NHibernate的开放资源项目就可以了)。

    最后,我想说:那些感觉ORM(例如:NHibernate)在项目后期的维护方面有问题,因而正在使用代码结构体,这样做,只会阻碍任何数据访问层的维护。刚才我说NHibernate是一个银弹,并不是意味着它把正确的应用程序设计的要求去除了。明智地从构架方面来考虑,对于输入一个.NET 应用程序到数据库,可能NHibernate是最可以提供最大维护的解决方案。

    不必说,NHibernate和其它ORM工具一样,减轻了成千上万的代码行和储存程序的维护,因而允许开发者将更多的注意力放在项目的核心上:及时领域模型和商业逻辑。即使你使用一个工具,例如:CodeSmith 或者 LLBLGen Pro,自动地生成你的ADO.NET数据访问层,在你的关系模型中退耦到你的领域模型时,NHibernate会提供足够的灵活性。你的数据库应该是一个“执行细节”,这个“执行细节”被定义来支持你领域模型,而不是用其他方法。这篇文章的剩余部分集中描述了使用已定制的设计模式和“从领域学到的知识”,以此来整合NHibernate到ASP.NET的最优方法。

2.本文目的及概述


   这篇文章假设读者非常了解C# 和 NHibernate,具有数据访问对象/ 属性模式方面的知识,至少基本熟悉泛型方面的知识 。注意,这篇文章没有着重在使用NHibernate,相反的着重在把NHibernate和ASP.NET融合成一体。如果你刚刚熟悉NHibernate,我建议你先阅读ServerSide.net: Part 1 和 Part 2里的两篇介绍。(时刻关注Pierre Kuate即将出版的NHibernate in Action)。如果想广泛地了解数据访问对象模式(在例子内综合利用了),就去看J2EE's BluePrints catalog。尽管我一直使用了“数据访问对象”或者("DAO"),在Domain-Driven Design中它和Eric Evans' 属性"是可以互换的。我刚发现"DAO"更适合这个类型。

   为了用ASP.NET 2.0应用程序构建数据层,我决心完成下面的目标:

   • 应该忽视表现层和领域层是怎样与数据库通信的。你应该能够最小程度地修改你的数据与这些层相通信的方法。

   • 在不依靠现场的数据库时,商业逻辑应该很容易测试。

   • Nhibernate的特点,例如lazy-loading,在整个ASPX页面生命周期必须是可用的。

   • 应该综合利用.NET 2.0 泛型 ,以此来减轻重复代码。

   两个示例应用程序,演示了NHibernate如何和ASP.NET一起使用,同时满足上述目标:

   • 基本NHibernate例子:这个例子解释了通过使用NHibernate和ASP.NET以及单元测试和易理解但不能完全再使用的结构体系的基本原理。

   • "Enterprise" NHibernate例子:这个例子有一个结构上的sound grounding(此sound grounding使用被证明的设计模式,此模式允许你在几乎任何大小ASP.NET的项目尽快地重新使用构架)。这个例子也展示了带有ASP.NET的NHibernate以及“其它资料集”(包括与多个数据库通信,使用模式Model-View-Presenter),建立简单的网络服务,此网络服务使用NHibernate,以及与Castle Windsor整合。包含了为了与多个数据库通信的代码。这篇文章没有包含详细的解释,在CodeProject.com的文章:Using NHibernate with Multiple Databases有详细的解释。(注意这篇文章示例应用程序与with NHibernate 1.0x,albeit相一致,仍然可以应用基本方法。

    下面描述的就是:这个示例应用程序怎样处理前面描述的每一个设计目标。但是在述说操作细节之前,让我们直接进入,操作例子。

3.运行基本的NHibernate示例程序

    示例应用程序,使用现有的SQL Server 2005中的Northwind,显示和更新Northwind中的Customer信息. 示范lazy-loading, 程序会显示每个客户建立的单.在本地运行示例程序所要做的就是安装有.NET 2.0 Framework的IIS,包括Northwind 数据库的SQL Server 2005 (或者2000)。(因为SQL Server 2005不会默认的包括Northwind数据库),你可以从2000简单地给Northwind DB备份,把它加载在2005里)。这个例子是SQL Server 2000...的接口,相应地,简单地修改NHibernate配置设置。

    使基本NHibernate示例应用程序运行起来:
 
   (1) 解压示例应用程序到你选择的文件夹
   (2) 在IIS内,创建一个新的虚拟的目录。化名是BasicNHibernateSample,而且这个目录应该指向BasicNHibernateSample文件夹,在解压应用程序后,就建立了此文件夹。
   (3) 打开BasicSample.Web/web.config和 BasicSample.Tests/App.config,修改数据库连接符,并连接到微软SQL Server上的Northwind数据库。
   (4) 如果,你在运行IIS 7,注释 "compatible with IIS 6" 配置节并解构the "compatible with IIS 7" 配置节. 以此来修改web.config。
   (5) 打开你的网页浏览器输入http://localhost/BasicNHibernateSample/Default.aspx,,程序就可以运行了。
在“Extending the Basics to an "Enterprise" Solution”中讨论了创建"企业"例子及运行它的步骤。但是在这之前,你要沿着你前面的basic sample,我们来看这个应用程序是怎样一步步来实现我们的设计目标的。


4.NHibernate 基本整合

   在开发应用程序时,我的目标是:
  • 写一次代码,并且只有一次
  • 代码的简单化和可读性
  • 最小程度的维护耦合和依赖
  • 保证关注点之间完全分离
  • 使用测试驱动开发来完成上面所有目标。
    这一章讨论,使用这些设计原理来把NHibernate整合到ASP.NET应用程序里。通过解剖Basic NHibernate 例子,我们就可以完成这些。

    构架备注

    这个基本示例不能被看成ASP.NET应用程序一个可以重新再度使用的构架,这一点非常必要。这个示例应用程序里的重点就是:以一种简单且明了的方式显示NHibernate整合。如果你在寻找“现实世界早已定制的”结构体系,看完这部分后,一定要记得阅读Extending the Basics to an "Enterprise" Solution。这个简单的示例确实介绍了一些最优方法技术和设计模式。
    分离接口

    分离接口,也就是依赖倒置,是在应用层之间建立一个完全分离的技术。Martin Fowler 和Object Mentor描述过这种技术,在Robert Martin的 Agile Software Development中有更清楚的描述。这种技术通常被用于清晰地将领域层与数据访问层去分开来。例如:假设一个客户对象-在领域层-需要与数据访问对象通信,以此重新找回一些过去的指令。(不管是不是在领域层,其它讨论中会有直接与DOA通信。)因此,客户对象在订单数据访问对象有一个从属器-在数据层。但是对于使用订单数据访问对象来返回命令,数据访问对象要求有一个倒置返回到领域层。

   最简单的解决方案就是把数据层(包括DAOs)放在同一个物理组件里,作为领域层。为了维护一个虚拟分离关注点,包含的项目包含2个文件夹,一个被称为域名,另一个被称为数据。(事实上,在以前的生活中,我在一个good-sized项目上使用了这一方法,产生了一些后悔的后果。)这个方法带来一些不好的后果。

   • 领域层和数据层双向依赖
   • 没有什么能阻止数据层流向领域层,反之亦然。如果在结构上它不能被阻止,那么它就会发生。
   • 不使用现场数据库来支持数据层,很难把单元测试领域层。在其他缺点之间,这个使得单元测试性能萎缩,因此,开发者停止了单元测试。

    相对应地,域名和数据层可以放在物理上分开的组件中;例如:分别放在Project.Core and Project.Data上。领域层(Project.Core组件)包括域名对象和DAO接口。数据层(Project.Data组件)包括DAO接口(定义在领域层)具体的实施方式。在下面的两个程序的集合图中有表示。注意箭头代表数据层到领域层的单向依赖。

    这个完整分离带来了很多好处:
    • 在结构上,开发人员不会在领域层直接使用具体的数据访问代码,当没有增加大量容易混乱的引用情况下。如NHibernate或者System.Data.SqlClient.
    • 这个领域层仍然无视数据层是怎样通信,或者与谁通信。因此,没必要修改领域层,确更容易找到数据访问执行的细节(例如:从ADO.NET到NHibernate)。
   • 在单元测试时,因为依赖于接口,就很容易给领域层提供一个“伪造”的数据访问层。这样可以保证单元测试快速运行并且不需要维护数据库中的测试数据。
在示例应用程序中包含了分离接口的执行例子,下面将进一步讨论。



依赖注入
  
    接口分离,就像上面所讨论的,导致了一个难题:当领域层仅仅知道接口时,怎样才能给予具体的DAO 执行情况。依赖注入 ,也就是我们所知道控制翻转(IoC),可以应用这个技术解决刚才的问题。有依赖注入 ,就可能删除具体对象的许多直接实例化,以及不灵活性(在直接调用新的关键词时,导致了不灵活性)。依赖注入可以通过手工或IoC容器来执行。人工的方法是简单地把对象所“依赖的服务”放入他的所在构造器或者属性中。我曾经在CodeProject写个一篇名为《Dependency Injection for Loose Coupling.》介绍这个方法。(“依赖的服务”可能是个DAO,是个邮件系统、或者任何其他外在支持、花费巨大的任何系统,或者一定不能在单元测试中被使用的资源)人工方法对于单元测试非常有用,因为在描述功能方面,清晰地使具体依赖关系的引用通过(单元测试)是很有帮助的。(Martin Fowler在详细的价值方面有很好的见解。),可能通过由代码或者XML 驱动的IoC Container 来操作依赖注入 。Fowler还写了关于IoC Containers和service locators的介绍,在两者之间做了一个正确的选择。想象下把IoC容器作为解耦的灵丹妙药. 一个IoC容器的优点包括一个包含灵活、松耦合框架和强调以接口开发。缺点是实施中增加了复杂性。在开发一个程序过程中,这是从多需要考虑灵活性和复杂性如何取舍的其中一个。这里有两个非常好的IoC容器工具以供使用:

   • Castle Windsor:通过使用XML 配置和强烈输入的declarations的混合体 ,Castle Windsor, IoC Container 提供了很多IoC支持。与其他选择相比较时,Castle Windsor带给表格的一些好处是更少的XML以及更多的compile-time error catching 。Castle Project还有一些其它强有力的模式,如果你寻找的不仅仅是IoC时,Castle Project使得它成为一个非常有吸引力的选择。这个IoC Container中有广泛的支持,在.NET发展团体里,IoC Container产生了许多争论。"Enterprise" NHibernate Sample包括使用Castle Windsor的例子。

    • Spring .NET:这个构造提供了IoC,这个IoC是通过XML配置文件定义的。像Castle Project一样,Spring.NET也提供了大量种类的额外发展工具。对来自于Java世界的开发者来说,这个选择特别诱人,而且这个选择早已与Spring Framework很熟悉。

    我发现:在测试单元外,使用一个IoC Container非常有帮助,使Aspx页面中单元测试获得依赖,也可以避免直接调用新的关键词。而且这将进一步在一个表示层或者服务层里连接依赖项 。

契约式设计

    Design-by-Contract (DBC)很简单,而且是不需要再一次使用调试器的最好办法。尽管没有经常讨论这种技术,但是这种技术应该得到和测试驱动开发所得到的同样的赞扬。(我并不是说DBC嫉妒,但是它应该得到那些表扬。)它提高了质量,减少了空检查,而且减少了调试时间,极大地提高了应用程序的总体稳定性。描写得更详细一点,DBC 是一种技术,在契约上对你的代码的用户负责(通常是你,或者你自己)以一种特殊的方式来使用方法和属性。因为这些方法和属性允诺它们将成功地完成给予的任务要求。如果没有遵循合同,就会导致异常。首先可能很严重,但是要经过很长时间才能提高代码质量。有DBC时,当调用方法或者属性的时候,前提条件就会调用契约任务必须遵循的东西。后条件调用契约任务被保证的东西。我非常推荐阅读introduction to Design-by-Contract这篇文章,快速使用它时,可以将你迅速地连接起来。这篇文章包括的例子项目使用一个被修改的DBC,而此DBC最初是Kevin McFarlane写的。当这篇文章的例子包含的变化维护合同任务,且无视编译模式时,原型允许有条件的编译,以此来为调用模式打开合同,或者为释放模式关闭合同。依我看来,一个合同总是一个合同,而且行为永远都不会改变。

    在这个代码中,你将发现Check.Require中定义了前条件,然而Check.Ensure定义了后置条件。因为DBC 不是domain-specific,这个类型被很正确地拔到了一个可再度使用的工具库,此工具库在"enterprise"例子里。
0