这是一个由EF群引发的随笔

平时在一个EF群摸鱼,日常问题可以归纳为以下几种:

这条sql用linq怎么写?

EF可以调用我写的存储过程么?

EF好慢啊一些复杂查询写起来好麻烦……

为什么会有这些问题?

因为EF是一个“ORM”。基本上这些问题都有一个共同点:将EF当作data mapping tool来使用,而不是ORM。

什么是ORM?

ORM是随着面向对象(OOP)而来的。很早的时候RDB一统天下,大家也习惯了面向数据的开发习惯(其实现在也是)。OOP出来后业界就发现了问题:RDB是基于数学理论的,而面向对象(OOP)是从软件工程的基本原则发展出来的,两套理论存在着阻抗,例如现在我们定义一个简单的博客对象:

    public class Blog
{
public Guid Id { get; set;}
public string Title { get; set;}
public string Content { get; set;}
public List<string> Tags { get; set;}
}

在这个博客对象中有个字符串泛型集合的标签属性,如果要持久化在RDB中一般用两种方法:1、标签单独一张表,Blog表与Tag表一对多关系;2、直接将Tags序列化(新一代的RDB提供的Json功能,所以一般是Json序列化)保存到Blog表中,用DDD的概念来说就是一种值对象(Value object)。

我们可以看到,第一种做法如果是直接将表映射到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,这个问题我们以后有时间再说。

关于ORM的浴室思考的更多相关文章

  1. 浴室沉思:聊聊DAL和Repository

    这是一个由DDD群引发的随笔 在写了上一篇随笔<关于ORM的浴室沉思>后一些朋友私聊我,很多刚接触DDD的朋友会对Repository(仓储层)这东西有点疑惑,为什么要叫仓储层?是不是三层 ...

  2. 领域驱动设计(DDD)的实践经验分享之ORM的思考

    原文:领域驱动设计(DDD)的实践经验分享之ORM的思考 最近一直对DDD(Domain Driven Design)很感兴趣,于是去网上找了一些文章来看看,发现它确实是个好东西.于是我去买了两本关于 ...

  3. 领域驱动和MVVM应用于UWP开发的一些思考

    领域驱动和MVVM应用于UWP开发的一些思考 0x00 起因 有段时间没写博客了,其实最近本来是根据梳理的MSDN上的资料(UWP开发目录整理)有条不紊的进行UWP学习的.学习中有了心得体会或遇到了问 ...

  4. CYQ.Data 从入门到放弃ORM系列:开篇:自动化框架编程思维

    前言: 随着CYQ.Data 开始回归免费使用之后,发现用户的情绪越来越激动,为了保持这持续的激动性,让我有了开源的念头. 同时,由于框架经过这5-6年来的不断演进,以前发的早期教程已经太落后了,包括 ...

  5. 关于领域驱动设计(DDD)中聚合设计的一些思考

    关于DDD的理论知识总结,可参考这篇文章. DDD社区官网上一篇关于聚合设计的几个原则的简单讨论: 文章地址:http://dddcommunity.org/library/vernon_2011/, ...

  6. CYQ.Data V5 从入门到放弃ORM系列:框架的优势

    前言: 框架开源后,学习使用的人越来越多了,所以我也更加积极的用代码回应了. 在框架完成了:数据库读写分离功能 和 分布式缓存功能 后: 经过三天三夜的不眠不休,终于完成框架第三个重量级的功能:自动化 ...

  7. ORM数据层框架的设计热点:更新指定的列的几种设计方案

    ORM框架的定义:对象-关系映射(Object/Relation Mapping,简称ORM) 常见的是:数据库结构=>映射Object(实体属性)=>基于实体类的操作. 还有一种:数据库 ...

  8. DDD实践问题之 - 关于论坛的帖子回复统计信息的更新的思考

    之前,在用ENode开发forum案例时,遇到了关于如何实现论坛帖子的回复的统计信息如何更新的问题.后来找到了自己认为比较合理的解决方案,分享给大家.也希望能和大家交流,擦出更多的火花. 论坛核心领域 ...

  9. Moon.Orm与其他Orm的技术对比

    有时候在思考大家为什么喜欢EF,为什么又出现这么多的Orm,为什么Nhiberate被人许多人接收又被许多人拒绝 最后发现结论:萝卜白菜各有所爱.适合自己的就是最好的. EF 微软团队支持(可谓强大的 ...

随机推荐

  1. Android查缺补漏--Activity生命周期和启动模式

    一.生命周期 onCreate():启动Activity时,首次创建Activity时回调. onRestart():再次启动Activity时回调. onStart():首次启动Activity时在 ...

  2. 快看Sample代码,速学Swift语言(1)-语法速览

    Swift是苹果推出的一个比较新的语言,它除了借鉴语言如C#.Java等内容外,好像还采用了很多JavaScript脚本里面的一些脚本语法,用起来感觉非常棒,作为一个使用C#多年的技术控,对这种比较超 ...

  3. Ubuntu SSH root 登录 Permission denied 错误

    问题: $ ssh root@40.125.21.75 root@40.125.21.75's password: Permission denied, please try again. 解决方式, ...

  4. jersey框架实现文件上传

    jersey框架是一个开源的RESTful的框架,实现了实现了JAX-RS规范,进一步地简化 RESTful service 和 client 开发.当然而且是必须的,jersey对文件的上传和下载也 ...

  5. 自学WPF之XAML(一)概念

    此笔记仅为学习过程中的一些总结,若有不妥之处,请指出,以便更正.下面直接进入正题. XAML:是一种设计UI的标签语言,是XML的派生语言,所以很多XML中的概念语法在XAML中均适用.在XAML标签 ...

  6. intellij idea svn使用一 导入、更新、提交、解决冲突

    大体上是转载,针对版本14有一些特殊的添加. 查看svn的资源库: 下面的多出了一个svn的窗口,在左边有加号可以添加一个svn的库 输入svn的地址,我用的是本地的测试,所以地址为svn://127 ...

  7. 如何用VSCode愉快的写Python

    在学习Python的过程中,一直没有找到比较趁手的第三方编辑器,用的最多的还是Python自带的编辑器.由于本人用惯了宇宙第一IDE(Visual Studio),所以当Visual Studio C ...

  8. rpm 命令详解

    参考:http://www.cnblogs.com/xiaochaohuashengmi/archive/2011/10/08/2203153.html rpm是由红帽公司开发的软件包管理方式,使用r ...

  9. MySQL 最左前缀(Leftmost Prefix) & 组合索引(复合索引,多列索引)

    资料来源于网络,仅供参考学习. CREATE TABLE test(a INT,b INT,c INT,KEY idx(a,b,c)); 优: SELECT * FROM test WHERE a=1 ...

  10. flask入门篇

    flask,Flask是一个使用 Python 编写的轻量级 Web 应用框架.其 WSGI 工具箱采用 Werkzeug ,模板引擎则使用 Jinja2 . Flask简单易学,属于轻量级的,学起来 ...