上次赶时间,就很流水账地写了上次项目对EF的一次实践应用模式,因为太长了,也没能探讨太多,所以再继续扩展。
这次想探讨的是,实体,如果作为类似于领域模型的业务模型存在,它的数据能否来自不同的数据源。这个想法首先是来自于这次应用中,Model First + 代码补充的方式形成了一个极好的效果。一方面满足了范式,减少了数据存储量;另一方面利用了封装特性,向业务提供了一个符合业务期望的实体。
首先看一个例子
我用Model First建立了Account一个实体,实际生成的代码如下:
public partial class Account { public int Id { get; set; } public string FirstName { get; set; } public string LastName { get; set; } }
对应地,上面三个字段分别对应着数据库中的三个字段。
然后,我再用Partial Class的方式,为Account进行扩充:
public partial class Account { public string FullName { get { if (this._fullName == null) { this._fullName = String.Format("{0} {1}", this.FirstName, this.LastName); } return this._fullName; } } string _fullName; }
这里就是关键了。此时,业务逻辑拿到的称之为Account的就不再是简单的DAO对象了,而是一个有着四个业务所需字段的业务模型对象。有兴趣的可以去看看我上一篇《Entity Framework 与 面向对象》中间设计模式的部分,里面讲到了为实体应用上了继承、多态、封装等面向对象的设计方式以达到业务模型所需的效果。
我的问题
这种为Entity添加完整面向对象特性的方式,有着一种想法,那就是能否让一个Entity完全抽象架空于存储层,允许一个Entity的数据来自不同的数据源。
首先,如果在存储层的视角来看,示例中Account的三个字段Id、FirstName、LastName来自于关系数据库,而FullName来自于计算值,也就是内存。站在数据库的角度上来说,FullName确实是不符合范式,所以很“多余”。
如果从时间线来说,在实例化的时候,前三个数据库字段的数据就已经被加载了,而FullName是Lazy Load。
但是,对于业务来说,他获得的不过是一个包含四个字段的Account的实例。
那么我的问题就继续扩展了,一个业务模型,能不能将数据源完全封装,在业务获得该实体的时候,完全脱离关注实现?
当我获得一个产品,那么这个产品的基本属性来自关系数据库,产品介绍来自NoSql,统计值来自动态计算。重要的是,这要是一个对象,而不是自行用多个对象拼装出来的。
实际上,是封装的问题
实际上,来到这里,就已经很明朗,这是一个封装的问题。我们从前遇到的是关系数据库和面向对象的阻抗失配,然后用ORM解决了。而今天,我们还可能遇到非强类型对象和非关系数据库以及强类型对象和关系数据库混合使用的问题,而且这使得其成为常态。
通常,因为一个特性的原因,我们要在技术上进行选型。要么用静态语言,要么用动态语言。但是当出现一个用另一种方式更合适的时候,实现就变得很别扭了。
比如用上面产品的问题,多少人是把产品详情作为整个文本值存在一个字段里呢?多少人又在用一张表来解决动态字段的问题呢?
实现,现实吗
在Account的例子中,这种行为很容易实现。一方面依托于EF提供了相关的支持,另一方面是两个存储源具有时间上的关联。在进行一项业务的时候,所有数据库里,也就是存储在持久化存储器内的数据,最终都要读取到内存中才能被操作。这种纵向的关系,方面了应该来自于内存的数据源在产生数据的行为嵌入到整个业务过程中。
但是当两个数据源没有纵向关系而是平行关系的时候,问题就来了。这个状况就跟当年的阻抗失配的问题类似。
首先是一个迭代的问题。
大家都知道,获取数据是有获取成本的。数据在抽象层面是“获取”,在实现层面是一次“拷贝”,拷贝的过程包括了“传输”。拷贝和传输就是数据获取的成本,这个成本在一些情形可以被很大的放大,比如从前的递归查询。
如果不把递归写成SQL,那么在业务层面进行解决的话,就要往返很多次数据库获取数据。产生的业务结果是一样的,但产生的性能结果差异极大。
而我假象的获取数据的行为是这样的:
可以简单当两个数据源的时候,会存在一个“数据汇合”的问题,其实就是一个同步和匹配问题。
两个数据源获取到的数据需要匹配上,在一个大量查询的时候就变得很麻烦了。如果按照顺序进行匹配不就简单了?但是要知道,“顺序”是个很奇怪的命题,你查找到的数据是一个集合或者列表才有“顺序”,对于NoSQL一类Key-Value的形态,如何定义顺序呢?
另外,如果是要一个数据源强行配合另一个数据源,那么一次数据获取行为的效率,就是由最慢的一个数据源决定的。
我再是想一下,如果是全部或者部分延迟加载,那么是不是说,读取数据都需要是异步的,而且合并数据的情况下,需要有一个用以合并数据的线程。
我打算尝试
实际会遇到什么情况不好说,所以我倒是乐于去尝试一下。
一开始的思路,是先尝试继续用扩充字段,然后把数据源获取封装好;然后我应该尝试去切入LinQ Provider,看看能不能在LinQ中下手,这对于.NET的使用或许更重要。