这是一个由 EF 群引发的随笔
平时在一个 EF 群摸鱼,日常问题可以归纳为以下几种:
这条 sql 用 linq 怎么写?
EF 可以调用我写的存储过程么?
EF 好慢啊一些复杂查询写起来好麻烦……
为什么会有这些问题?
因为 EF 是一个 "ORM".基本上这些问题都有一个共同点:将 EF 当作 data mapping tool 来使用,而不是 ORM.
什么是 ORM?
ORM 是随着面向对象(OOP)而来的.很早的时候 RDB 一统天下,大家也习惯了面向数据的开发习惯(其实现在也是).OOP 出来后业界就发现了问题:RDB 是基于数学理论的,而面向对象(OOP)是从软件工程的基本原则发展出来的,两套理论存在着阻抗,例如现在我们定义一个简单的博客对象:
在这个博客对象中有个字符串泛型集合的标签属性,如果要持久化在 RDB 中一般用两种方法:1,标签单独一张表,Blog 表与 Tag 表一对多关系;2,直接将 Tags 序列化(新一代的 RDB 提供的 Json 功能,所以一般是 Json 序列化)保存到 Blog 表中,用 DDD 的概念来说就是一种值对象(Value object).
public class Blog
{
public Guid Id { get; set;}
public string Title { get; set;}
public string Content { get; set;}
public List<string> Tags { get; set;}
}
我们可以看到,第一种做法如果是直接将表映射到 entity,那么我们最终得到的 Blog 类型可能并不是根据业务设计出来的样子,也就是说业务对象为 RDB 持久化的技术而妥协设计了.第二种方法看起来不错,但已经属于 newsql 的范畴,和 RDB 无关.
因此我们要明确的概念是:ORM 是为了解决阻抗失配的,没有解决阻抗失配的都不是 ORM,包括 dapper,Mybatis 等等等等(所谓 micro orm 其实并不是 ORM),后者更适合的叫法应该是 DMT(data mapping tool),只提供了表和 entity 的映射或者表达式树的处理.在 DotNet 这块,真正的 ORM 只有 EF 和 NH 两者(天国的 linq to sql 也不算,因为其只提供了 DB First).
扩展阅读:阻抗失配不仅仅是上述问题那么简单,例如 OOP 三要素——封装,继承,多态,假如你的业务对象存在继关系,那么在 RDB 中该如何描述?EF 中提供了 TPH (Table Per Hierarchy,父子类在同一张表,EF 自动添加 Discriminator 字段用于标识属于哪一类型),TPT (Table per Type,父子类在不同的表,子类表只包含子类属性,通过相同的 Id 来关联父类表上相同的 entity 父类数据) 以及 TPC(Table Per Concrete Type,没用过,也没见人用过,父子类在不同的表,父类的属性在子类表中也会存在,估计是为了优化 query) 三种方式,大家可以找下资料,在这里不做展开.
如何优雅地使用 ORM
正确地使用 ORM 第一个前提是,项目必须是 OOP 设计,解析业务后先进行业务对象的建模,然后再通过 ORM 持久化业务对象的状态.以 EF 为例,基本排除了 DB First 以及 Model First 的做法,因为后两者属于面向数据库设计,所以 EF Core 只保留 Code First 除了更为精简外,其实也更符合 ORM 的实践.
然而:
这没有解决搜索(query)问题啊.
在这里我们要了解另一个概念——CQS(命令查询分离).
CQS 最早提出于 1988 年 Bertrand Meyer 的《面向对象软件架构》,可以归纳为 "原则上一个方法不应该对数据造成影响(增删改)的同时又返回数据".以是否对数据造成影响我们可以将操作分成两类:
查询(Query):返回数据,不修改数据,不会产生副作用.
命令(Command):修改数据,不返回数据,遵守单一职责原则.
在具体落地的项目中,查询往往千变万化,复杂的查询甚至要多表链接(大于 3)还要进行聚合处理.其实我们可以看到,这里的查询几乎可以当作是弱报表——而几乎所有的这些查询,都不是 OOP 的功能.
因此虽然可以实现复杂查询,但 ORM 并不适用于 CQS 中的查询(Query)端.更好的做法是将项目的功能分成命令和查询两块,然后只在命令端使用 ORM,Query 端怎么快怎么来——当然具体实现也可以两边都用 EF,但 C 端要当作 ORM 用,Q 端直接执行 sql 语句.
扩展阅读:CQS 并不是死规矩,例如 stack 的 pop 操作,有返回结果的同时也会改变 stack 本身.CQS 落实到实际项目中并不是真的将操作简单地分成两类,比较简单的分法是:页面展示的一般是 Q 端.一些专业的项目 C 端甚至可以只通过 Id 来获取业务对象,这有助于仓储层的服务化以及事件溯源(Event Sourcing)的实现,以及在分布式系统中处理幂等.
最终总结,正确的使用 ORM 并没有想象中那么简单也没有那么难,其实也就是两条经验之谈:
1,必须是 OOP 设计,先使用 Code First 建好业务模型后再考虑如何持久化.
2,不能为 Query 而妥协设计,如果真的出现相对复杂的查询,直接 CQS,Q 端可以使用 Dapper 甚至 Ado.net 实现.
到了最后聊聊一些题外话,现在已经有各种 DDD 框架,但用了 DDD 框架并不代表你的项目就是 DDD.同时有些 DDD 框架的实现就有待商榷,例如 ABP 其仓储层的设计就存在问题,因为它持久化的并不是 DO(Domain object)的状态而是 PO,这导致 ABP 的项目更类似于 DDD Lite,这个问题我们以后有时间再说.
来源: https://www.cnblogs.com/Mutuduxf/p/8276425.html