POCO真那么重要么?
不断听timiil向我介绍Entity Framework 4.0的诱人之处。当然,他知道我最需要的是稳定且实用的设计时和提供给我从模型到数据库的设计体验,这两点是EF1.0不能满足我的。之所以我特别在意这两点,是为了满足我经常性修改模型的现实要求。显然,EF 4似乎有所改善,但显然离我的要求仍然有相当大的距离。
刚看到的一篇介绍EF4的文章,似乎比较喜欢POCO,这让我相当意外。那么,POCO真那么重要么?
我不知道除我以外还有谁在实际项目中用过ADO.NET Entity Framework,我用过,从一开始的饱受折磨直到想放弃,到后来越来越令人舒服,过程是漫长了一点,但我觉得这个过程还是值得的。在我的项目中,大约有60+个实体类,继承深度最深的地方是三层,可以参见实体图(担心你看不清,但是又不知道如何上传1:1的图):
从中间那个PointBase看开去,可以发现一共有15个派生类。这也是迫不得已的事情。地球人都知道,我一定会被可恶的edmx困扰。确实是这样,特别是当模型即将发生变化的时候,往往就是噩梦的开始。因为在EF1环境下我只能手工修改数据库,然后再来更新模型。仅仅把类似Point实体集名称修改成Points就得花费我相当多的时间,更别说这些复杂的继承和关联关系了。我甚至动过念头,自己写一个Visual Studio插件来维护edmx。当然,到后来我发明了“局部固定法”,能够很轻松地控制这个过程了,我甚至可以纯手工修改edmx文件而不需要用EF的设计时来代劳。
无论如何,ADO.NET Entity Framework有两点让我相当满意而且目前暂时无法被替代:
1.一个相当稳定且高效的运行时,这方面从一开始都没有给我制造过麻烦,只是刚开始时有些不熟练需要不断摸索,并且需要经常性地向timiil讨教;
2.Linq不仅给我省去了写SQL的烦恼,令我合并或分拆查询因子极为方便,还可通过传递Expression用于灵活地控制业务关系,没有任何一处需要硬编码。
我不知道那个恶心的HQL是如何进行查询因子的合并和分拆的,在我看来根本就是开倒车,非常困惑那些叫好声是基于什么,难道都是人云亦云么?
花点时间举例解释一下“查询因子的合并和分拆”吧。用户登录进来后,会建立一个安全上下文存放在Session中,一是限定这个用户所在的领域和区域(领域就是水环境、大气环境或噪声环境;区域就是行政区域-某省或某市了) ;二是这个用户有很多的个性设置,例如排序习惯、显示栏目设置等。这个用户的每一个查询最终被执行的时候必须受当前业务限定的静态查询条件、当前操作中的动态查询条件和安全上下文因素的三重控制。大部分情况下,这些因素是不确定的,是一个可以任意配置的。这些众多的因素有些是取并集,有一些是取交集,我习惯上将这两个过程分别称为查询因子的合并和分拆。
我想说的是,我从来没有希望EF支持POCO,这个所谓的POCO根本就是伪命题。你希望维持实体类不被污染的原因无非就是传来传去对吧?当你需要的时候,你直接另外再写一个DTO不行么?依据我长期使用ORM的经验,透明地实现POCO到数据库你会得不偿失。由框架来完成的数据库设计通常是相当潦草,惨不忍睹,五年前我使用ECO的时候便深受其害。这是其一。通过手写的POCO来“再”映射实体对象,可有效地隔离对象模型和数据模型,令业务清晰可辨。例如,经度和纬度在数据库中可以用一个float来表示,而在对象空间,我希望是另外一个struct,一是可以ToString()成“E116°55′03″”或者“N23°40′17″”的样子,二是可以在对象空间简单地做一个对double的隐式类型转换的重载。显然,这样会令你保持足够的灵活度,而你却不能指望你的框架能有效地识别你的的这些精心设计,并在数据库中体现。这是其二。有一个规则,在业务构建过程中,你永远都不要传递对象集合,而是传递一个Request对象,例如Expression。这些Request不到最后一刻是不会连接数据库的。就这个规则来说,真正需要传递的实体对象相当少,即使需要传递,你所需要传递的对象通常是需要通过另外一道封装的,与实体对象相差甚远。即使EF4提供了POCO,我也绝对不会传递任何实体对象的。这是其三。
那么我需要什么?
一个改良的edmx的设计器(相比运行时,我通常叫设计时),在这个设计器中我可以加各种标记,按我的标记来生成数据库。永远不要从数据库更新模型,而是从模型更新数据库,且更新的时候不能破坏我已有的测试数据。象继承关系这些复杂操作可以很快解决,而不是象现在需要四步才能完成。这是其一。EF1的QueryableProvider功能还是弱了一些,对表达式的识别能力相当有限,并且无法由开发者自己简单地扩充。所以经常会抛出一些NotSupportedException,你不得不想一些BT的方式来绕开或者分解成多步。象
context.DataEntries.Where(e => e.Times == times && ((Point)e.PointBase).Name == pointName && e.MonitoringItem.Name == monitoringItemName)
.Select(e => e.LowestCase.HasValue ? e.LowestCase / 2 : e.Average).Average().Value
这样的语句,我认为是非常常见的,应该予以支持。 这是其二。
总而言之、统而言之,切切实实提升生产力才是王道。