关于ORM的浴室思考
这是一个由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的浴室思考的更多相关文章
- 浴室沉思:聊聊DAL和Repository
这是一个由DDD群引发的随笔 在写了上一篇随笔<关于ORM的浴室沉思>后一些朋友私聊我,很多刚接触DDD的朋友会对Repository(仓储层)这东西有点疑惑,为什么要叫仓储层?是不是三层 ...
- 领域驱动设计(DDD)的实践经验分享之ORM的思考
原文:领域驱动设计(DDD)的实践经验分享之ORM的思考 最近一直对DDD(Domain Driven Design)很感兴趣,于是去网上找了一些文章来看看,发现它确实是个好东西.于是我去买了两本关于 ...
- 领域驱动和MVVM应用于UWP开发的一些思考
领域驱动和MVVM应用于UWP开发的一些思考 0x00 起因 有段时间没写博客了,其实最近本来是根据梳理的MSDN上的资料(UWP开发目录整理)有条不紊的进行UWP学习的.学习中有了心得体会或遇到了问 ...
- CYQ.Data 从入门到放弃ORM系列:开篇:自动化框架编程思维
前言: 随着CYQ.Data 开始回归免费使用之后,发现用户的情绪越来越激动,为了保持这持续的激动性,让我有了开源的念头. 同时,由于框架经过这5-6年来的不断演进,以前发的早期教程已经太落后了,包括 ...
- 关于领域驱动设计(DDD)中聚合设计的一些思考
关于DDD的理论知识总结,可参考这篇文章. DDD社区官网上一篇关于聚合设计的几个原则的简单讨论: 文章地址:http://dddcommunity.org/library/vernon_2011/, ...
- CYQ.Data V5 从入门到放弃ORM系列:框架的优势
前言: 框架开源后,学习使用的人越来越多了,所以我也更加积极的用代码回应了. 在框架完成了:数据库读写分离功能 和 分布式缓存功能 后: 经过三天三夜的不眠不休,终于完成框架第三个重量级的功能:自动化 ...
- ORM数据层框架的设计热点:更新指定的列的几种设计方案
ORM框架的定义:对象-关系映射(Object/Relation Mapping,简称ORM) 常见的是:数据库结构=>映射Object(实体属性)=>基于实体类的操作. 还有一种:数据库 ...
- DDD实践问题之 - 关于论坛的帖子回复统计信息的更新的思考
之前,在用ENode开发forum案例时,遇到了关于如何实现论坛帖子的回复的统计信息如何更新的问题.后来找到了自己认为比较合理的解决方案,分享给大家.也希望能和大家交流,擦出更多的火花. 论坛核心领域 ...
- Moon.Orm与其他Orm的技术对比
有时候在思考大家为什么喜欢EF,为什么又出现这么多的Orm,为什么Nhiberate被人许多人接收又被许多人拒绝 最后发现结论:萝卜白菜各有所爱.适合自己的就是最好的. EF 微软团队支持(可谓强大的 ...
随机推荐
- 基于QT的异质链表实例
所谓的异质链表就是的节点元素类型能够不同.本实例採用C++抽象类和多态实现. #include <QApplication> #include<QPushButton> #in ...
- html5 canvas 画板
<!doctype html> <head> <meta http-equiv="Content-Type" content="text/h ...
- netty开发教程(一)
Netty介绍 Netty is an asynchronous event-driven network application framework for rapid development o ...
- IDEA快速创建Maven+SpringBoot项目时:Cannot download https://start.spring.io;Status:403
先展示一下我遇到的问题: 用浏览器搜索是有页面的,但是但是但是呢,用IDEA快速构建的时候就报403 咳咳!巴格虐我万千遍,我待技术如初恋... 我看到的解决办法有以下两种,当然,我只想说:" ...
- 7.python常用模块
1.time 常用表示时间方式: 时间戳,格式化的时间字符串,元组(struct_time) UTC(Coordinated Universal Time,世界协调时)亦即格林威治天文时间,世界标准时 ...
- 数据分析与展示——Matplotlib基础绘图函数示例
Matplotlib库入门 Matplotlib基础绘图函数示例 pyplot基础图表函数概述 函数 说明 plt.plot(x,y,fmt, ...) 绘制一个坐标图 plt.boxplot(dat ...
- Hibernate问题浅析
1.什么是SessionFactory?什么是Session?httpsession和hibernate的session的有什么区别? SessionFactory接口负责初始化Hiber ...
- 520. Detect Capital
Given a word, you need to judge whether the usage of capitals in it is right or not. We define the ...
- js 变量、作用域和内存问题
基本类型和引用类型 5种基本类型:undefined.null.boolean.number.string 引用类型:由多个值构成的对象 属性 引用类型可以动态添加属性,而基本类型不可以 var p ...
- ActiveMQ (三) 讯息传送机制以及ACK机制
详析请看如下博客: http://blog.csdn.net/lulongzhou_llz/article/details/42270113 后续再做整理.