引言
面向对象的领域模型与面向关系的数据库,如同在二维平面上绘制三维的物体,始终充斥着表达与实现间的种种羁绊。
为此,PoEAA(Patterns of Enterprise Application Architecture,企业应用架构模式)与DDD(Domain Driven Design,领域驱动设计)应运而生。Martin Fowler在总结了大量的应用经验后,提炼出若干种应用于企业应用开发的模式,并最终使PoEAA一书成为了企业应用开发领域的圣典。Eric Evans提出的以领域模型和敏捷开发为核心的领域驱动设计,使得PoEAA得到迅速的推广与普及,DDD本身也因此得到了极大的丰富与完善。
与之呼应地,Microsoft迅速在.NET平台上推出了LINQ(Language Integrated Query,语言集成查询)与Entity Framework,以期为.NET的企业应用开发铺平道路。但归咎于.NET Framework的臃肿、效率和部署等因素,加之一定程度上的误解,LINQ与EF的推广遇到了相当的阻力。CSDN上类似《为什么很多公司都不用LINQ》这样的帖子,便可作为例证。而在另一方面,EF 4.3 release已于2月9日发布,EF Migration进步神速,在国外各大技术论坛上引发了热议,与之形成了鲜明对比。
有鉴于此,我从一名普通.NET程序员的角度出发,谈一谈我为什么需要LINQ与EF,亦将此作为对自己学习与实践的简要总结。因为,我宁愿奔劳于追日的坎途,而不愿意成为井底那只安逸的青蛙。
还要写在前面的是,PoEAA与DDD的出现,并不只是为了解决领域模型与关系数据库之间的失配。最后,LINQ不只为数据库而来。
在没有LINQ与EF的日子里
在那些没有LINQ与EF的日子里,应该说是再早一点,在没有.NET与C#之前,Visual C++、MFC、COM和ADO几乎成为了我开发MIS(Management Information System,管理信息系统)的全部,Hopper Island News成为我学习和使用ADO的样板。那些日子里,我一边看着Borland与Sybase的江河日下,一边彷徨于Microsoft与Sun关于Java标准的争执,加之不习惯Java略显怪异的语法,而最终没有投入Java的怀抱。
相比C++里令人头疼的指针使用、线程和内存的管理,Java诸如内存自动回收之类的特性,在当时是一个很大的亮点,也成为许多人投身Java的主要原因。慢慢地,随着STL与ATL的普及,一些通用的模板和框架被大量引入C++,编写COM不再显得困难非凡,情况开始有所改善。但是如同今天许多人在ADO.NET基础上编写SqlHelper(或DbHelper)一样,我当时仍要费很大的劲去对RecordSet这样的东西进行再次封装,小心地处理数据类型的转换,谨慎地实现线程的同步,精心地设计查询所需的排序与查找算法,还得异常敏感地去查找所有可能的Bug与内存泄漏。我的时间与精力,因此而大量地花费在了这些底层的实现细节上。期间,我甚至为了减轻设计负担,尝试了IBM的VisualAge C++。
接下来的某天,我收到了MSDN寄来的一份邮件,内有一套Visual Studio.NET 2002 beta的CD和一本惯常以“Hello World!”开始的C#编程手册。类似C++的语法、类似Java的内存管理和类似Visual Basic的UI设计方式,都在很大程度上吸引了我。转向C#之初的这段日子,我并没有太多的喜悦,因为转轨总是伴随着痛苦。另一方面,宽带上网尚显奢侈,资料收集极其不易,加之C#非常稚嫩,.NET Framework与ADO.NET存在许多让人诟病的地方,所以我的进步非常缓慢。尽管如此,C#堪比Pascal的严谨语法,ADO.NET的焕然一新,更重要的是C#与.NET让我逐渐从底层的细节脱身,都给我带来了极大的振奋和希望。
在临摹Duwamish的过程中,我等到了NET 2.0的发布,PetShop转而成为了我模仿的样板。然而,.NET的ORM工具仍相对匮乏,结合ADO.NET、泛型和原生的SQL语句制作的SqlHelper大量涌现。这些SqlHelper的质量参差不齐,导致SQL注入攻击成为了这一时期许多网站管理者的噩梦。某个“五一”帮朋友配机时,就曾遇到卖家炫耀其基于.NET 1.1开发的进销存系统,结果被我当场注入,最后他以免费赠送一根内存条为代价,换取了一个现在看来是非常勉强的解决方案(警告:千万不要把程序员与电脑折装工划上等号)。
俱往矣!
LINQ和EF来了
接下来的若干年里,各种设计工具和框架接踵而至,底层的实现越来越多地被掩盖和隐藏,让我们可以更加专注于领域模型的构造,而免受内存管理、线程同步和数据库访问等具体实现的干扰。尽管如此,对数据进行查询、排序、分组、聚合的一些基本功能,仍主要依赖于自定义的算法实现。
期间,Hibernate被移植到.NET平台,成为了当下依然流行的持久化框架、ORM工具NHibernate。NHibernate利用反射Reflection与元数据Metadata实现了ORM,并提供了类似SQL的HQL查询语言(Hibernate Query Language)以及条件对象(Criteria Object),极大地解放了数据库访问的实现,使PI设计(Persistence Ignore,持久化无关)变得越发容易。
然而,强大的工具,通常也意味着相应的复杂度与相当的难度。就在我被NHibernate配置弄得头晕脑胀的时候,LINQ to Objects首先进入了我的眼帘。当只用一个函数OrderBy()就可以取代之前写了若干行的排序算法时,我知道,又一个新的时代到来了。
从DDD的角度理解LINQ与EF
我无意于在此阐述LINQ与EF究竟是什么,而只想说明以下几点:
1. LINQ ≠ LINQ to SQL,LINQ ≠ LINQ to Entity,不要一提到LINQ,就把它与数据库挂上钩。
2. 除了LINQ to SQL、LINQ to Entity以外,LINQ to Objects、LINQ to Xml、LINQ to DataSet也是LINQ的重要组成。其中的LINQ to DataSet,更是Access与Excel相关应用的利器。
3. 包括NHibernate、iBatis在内,为了满足PI需要,所有的ORM工具都会有效率上的损失。
4. 原生的SQL语句尽管强大,但无法避免不同数据库之间的兼容性问题,以及提供优雅简洁的代码。
那么具体到实际中的应用开发,我们又将如何理解和定位LINQ与EF呢?
“专注于领域模型构造,迭代与重构以完善设计”—这是我个人对DDD的概括。DDD采用了分层的理念,因此同样遵循了层内高度内聚、层间尽可能松耦合的设计原则。一个理想的DDD成果,领域模型可以无需重新编译而自由选择Web或WinForm的UI实现,通过简单的配置就可更换不同的数据库支持,甚至使用非数据库的形式作为持久化支持。
在这里,LINQ在DDD的各层均可充分应用,因为它本身就是C#语法的一部分,解决了我们最常用的数据排序、查找、分组、聚合等功能,其灵活性、扩展性在Specification模式的实现上更是体现得淋漓尽致。
而在基础层的数据库访问实现上,以LINQ to Entity为代表的Entity Framework开始走上前台,成为.NET平台下新兴的ORM工具。借由Code First对POCO(Plain Old CLR Object)声明式的特性修饰、简洁的Fluent API和复杂的Object Services,EF为Unit of Worker、Identity Map、Data Mapper等PoEAA模式的简洁实现提供了强大支持。EF Migration则在不变更领域模型设计的前提下,可以变更数据库设计而不破坏原有数据,或进行数据的迁移,尽管我还不是非常熟悉它。通过各种定制的Provider,我们还将SQL的兼容性问题,部分地推给了数据库厂商。
在EF下使用Code First的方式,避免了NHibernate繁琐的配置过程,转而改用一些直观的特性修饰和内嵌的数据库构造器,确保了代码的简洁易读和对数据库更全面的控制。如果说二者同作为ORM工具,有一定程度的对应关系,那么可以把EF 里的DbContext对应于NHibernate里的ISession,但是后者通过HQL与查询对象实现的查询,就没有EF中的LINQ表达式来得简洁和优雅了。
在学习和实践EF Code First的过程中,我完成了一个小项目的升级。通过装配不同的数据访问程序集,达到修改POCO与表之间映射关系的目的,成功实现了数据库中表的垂直分割与水平分割,而未影响其他层的实现。但是,期间也暴露出一些问题:
1. POCO类要承载许多类似[Key]、[Table]之类的声明修饰,使POCO对象变得臃肿,并且变相地与数据库建立了耦合。
2. 复杂的映射逻辑仍需要通过重载OnModelCreating()实现,又再添POCO类与底层间的耦合。
3. LINQ to Entity仍存在一些问题,Include()方法时常会遇到类型失配的运行错误。
因此,LINQ与Entity Framework还有很长的路要走。
写在最后
有的人喜欢NHibernate或者iBatis,有的人喜欢EF,也有的人喜欢原生的SQL,这本无可厚非。
如何挑选工具,去实现业务领域的需求,这本身就是一个权衡与决策的过程,也谈不上被谁绑架的问题。就如同我可以说NHibernate的配置繁琐,而你可以说EF仍不够灵活,又或者我说原生SQL影响代码的移植与可读性,而你说EF没有原生SQL高效一样,世上本没有十全十美的东西。
通过以上的说明,我之所以选择LINQ与EF,可以归纳下以下几点:
1. LINQ提供了一致的查询语言,提高了代码的可读性。
2. LINQ提供了常用算法,减轻了编码负担,提高了代码的可维护性。
3. 我喜欢Code First简洁的修饰,不喜欢繁琐的XML配置。
4. 我很期待EF Migration
所以,放下固有的成见、摘下有色的眼镜,停止无意义的争执吧。如何高效、灵活、便捷地实现应用,才应该是讨论的重点。