引言
自从 .NET 真正走入开发人员那天起,“效率”两个字就一直成为众多程序员津津乐道的话题。无论是从开发模式(Cross Language)、系统框架(.NET Framework),还是各种使用方便的工具(VS.NET),无一不体现出了它的胜人一筹。
同时,在另一方面,.NET 是否可以真正胜任企业级应用(Enterprise Application)开发的重任,却依然争论不断,褒贬不一。
通常来说,对于一个企业级应用,需要考虑的方面很多,如安全、性能、伸缩性、易用性等。在本文中,作者更愿意与大家一起探讨 .NET 下数据访问层的相关技术,这可能是在多层架构(n-Tier Architecture)诞生之日起就受到广泛关注的敏感话题,而对于大部分开发人员来说,这也可能是项目中最让人沮丧的部分,甚或引起争议最多的部分。
在以下论述中,为统一起见,作者暂时将数据访问层简称为DAL(Data Access Layer)。
分析问题
简单统计分析后,就不难发现,DAL之所以让人畏惧,并非出于技术本身的问题,甚至恰恰相反,很多开发人员认为这是最没有技术含量的部分之一(就作者经历的大小项目来看,该层所占的开发时间一般较短,也是很多开发人员不愿意承担的“苦差”),只是架构需要或者某些思想作怪(如:为DAL而DAL)才加入了这所谓的第四层(传统三层架构并没有提出DAL思想)。
DAL的提出,确实对传统的架构模式提出了巨大挑战,加入的目的肯定也是希望借其进一步提高生产效率,在这种模式下,理想情况是:大部分开发人员从此摆脱DBA之苦,甚或彻底断绝与数据库的直接关系,SQL之痛将离我们而去,整个OO世界从此清静。
不过,理想归理想,能否成为现实则需通过项目检验。
接下来,作者试图分析比较流行且较有代表性的几种解决方案,看看能否从中得出一些有价值的结论,并为我们今后在设计与实现DAL时提供一些借鉴。
ADO.NET
首先,提到.NET下的DAL,马上映入眼帘的就是ADO.NET。
没错,几乎所有的DAL解决方案(请答应作者使用Solution而非Framework)都必须从它发展而来,没得选择,这也是具有.NET特色的实现方式(相比较J2EE)。
排除商业因素及CLR本身的需要,ADO.NET真正带给我们的东西不多,值得一提的也就DataSet(就作者经历的项目来说,使用更多的是DataTable和DataView)。从微软早期的内存数据库(Memory Database)鲜有人问津到今天的DataSet大行其道,这其中的曲折实非片言只语所能道尽,总之,有一点可以肯定,正是有了DataSet这种选择,.NET下的DAL才能象今天这般百花齐放,大家的思路才能更趋开阔。
Duwamish
这方面有很多好的Sample,最经典的莫过于微软大力推荐的企业级开发套餐:Duwamish。对于希望学习.NET下DAL设计的朋友,这是一个不错的起点,这方面的完整剖析,大家可以参考“CSDN开发高手,2003.11”,本文不再赘述。
作者自己参与的一个项目中就使用了Duwamish方案,当时限于工期,感觉这是一个很好的参考,没做深入分析就开始设计了。现在回想起来,发现还是有很多不足之处。
举个简单的例子,Duwamish方案中并没有考虑Cache Management,而这对于企业级应用来说,某些时候就是一个不得不考虑的问题;另一方面,虽然Duwamish中离别了SQL语句(全部采用存储过程实现),但数据库痕迹依旧十分明显,比如:某些字段名称的定义,关联表名称的定义等等。
还有一个十分头疼的问题是在开发过程中体现出来的。一开始,那些比较简单的数据表还比较轻易实现,到了一些包含相互关系的数据表时,我们的DAL工程师就感到了压力,到后来,几乎又做了一遍DBA在数据库建模时早已做过的工作,只不过,这次将数据库脚本换作了C# 实现(或者说:将数据库结构换成了表面上具有OO特色的DataSet)而已。
可能,Duwamish的实现比较经典,但在实际应用中,有时并不意味着Best Practice。就拿我们的项目来说,虽然成功交付,但无论从模型复用角度,还是开发效率来说,都不能算很成功。套用一句流行语:其实我们可以做得更好!
PetShop
ADO.NET上另一个值得参考的DAL实现就是鼎鼎大名的PetShop。
当然了,与Duwamish相似,名气大未必真的实用。PetShop虽然弥补了Duwamish在某些方面的不足,例如:通过Factory支持多种数据库存储,引入了Cache机制,提供了更为便利的SQL Helper,但也同时带来了另一些问题。其中,最麻烦的就是SQL语句的引入,而且还是针对不同数据库存储的不同SQL语句(主要是SQL Server与Oracle的参数表示方式不同)。
另一方面,PetShop虽然没有使用DataSet而代之以更为简洁的普通实体对象(Model),但它还是将DataReader的结果转换到了包含实体对象的列表集合中供离线使用,从这个意义上说,可谓换汤不换药。甚至,在某些场合,例如:需要进行数据过滤,或者在主从数据间导航,反而更为不便(此时,简单的Collection或者List是无法满足需求的,DBA与DAL开发人员只能再提供其它的方法来达到目的)。
从上述两个例子中,我们可以看出,即使在微软的开发团队中,也没有能够在DAL这个问题上达成一致。这方面的更具体信息,有爱好的朋友可以参考如下文章:
http://www.microsoft.com/china/community/Column/67.mspx。
实战
上面剖析的两个解决方案,让我们看到了它们各自的优势与不足,而企业级应用的复杂环境也不太可能要求一个放之四海而皆准的框架就能解决所有难题,因此,只能根据具体情况具体分析。作者曾经参与一个(.NET)大型外包项目的开发工作,有幸一睹其DAL的设计思想,深感震撼,在此与各位朋友一起共同探讨。以SQL Server所带Northwind数据库为例,如下就是一段基于该DAL的调用代码(作者做了一些名称上的调整):
// 根据EmployeeID返回其Title
boEmp = new EmployeeDAL();
boEmp.Keys[“Emp_ID”] = 1; // 注重:实际字段名为:EmployeeID
boEmp.Select();
string strTitle = boEmp[“Emp_Title”]; // 注重:实际字段名为:Title
……
// 根据 City 返回所有符合条件的 Employee
boEmp = new EmployeeDAL();
boEmp.Keys[“Emp_City”] = “Seattle”;
boEmp.Select(); // 注重:该方法与上面的调用完全相同
DataTable dtEmp = boEmp.Table;
假如不考虑对象创建(可以采用Object Pooling或者Cached Object)以及调用后的处理,实际的代码只有两行!
更让人吃惊的是,上述EmployeeDAL类没有任何真正意义上的实现代码,仅仅是声明了类名,然后从一个通用基类继续而已!!
最优雅的地方还不在于此,实际上,就算在那个基类中,也根本看不到SqlConnection或者OracleAdapter之类的帮派之争。
相信大家也猜出来了,没错,它是借鉴了PetShop的实现,采用了Factory模式来保证DAL可以适用于不同的数据库存储。不过,这种实现与PetShop还是有很大的区别:至少,它没有产生不同的SQL语句,更没有出现不同的参数调用方式(SQL Server中一般使用“@”符号,Oracle中一般使用“:”符号),所有帮派一视同仁!
这其中,当然得益于Factory的实现技巧,但更重要的因素还在于设计方式的精妙。其实,在.NET Framework中,已经提供了这种设计方式的基石,说白了,就是System.Data中的那些Interface(如:IDBConnection,IdataAdapter等)。
在这样的设计基础上,我们针对每一个DAL类,就不再需要为不同的数据库存储提供不同的数据存取实现了。例如:在PetShop中,针对订单数据需要实现Order类,很自然的,系统为SQL Server与Oracle分别实现了Order类并使用不同Provider(SqlClient,OracleClient)提供的方法进行操作。而在实际调用时,PetShop通过Factory模式动态创建真正的Order类并激活相应的方法,一个面向不同数据库存储的方案就跃然纸上。
其实,PetShop这种方案已经比较灵活了,假如更能省去“撰写不同Order类”之苦,那就真的送佛送到天了J。而所有这些功能,在作者所参与的这个项目中,已经完全搞定了!
至于上面的“EmployeeDAL(当然,包括其它所有DAL类)没有任何真正实现代码”,只不过玩了一个小小的配置技巧而已:将不同的DAL类与相关的Stored Procedure(请注重:不是Table或View)按照Namespace分别存储到XML文件中。
可能大家已经看出来了,理论上,甚至只需要一个DAL类就可以完成上述所有的工作!但在实际操作中,不同的DAL类可能还是有一些数据处理上的细微差别(比如:数据校验,格式转换等)。
总的来说,在这样一个大项目中,不可能要求所有开发人员(除了DBA,DAL Framework Developer)都去了解ADO.NET的方方面面,虽然作者对此颇有研究,但在这个项目中,却从头至尾只用到了两个类:DataTable,DataView(甚至连Transaction都无需了解)!