这是一个由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. javascript变量:全局?还是局部?这个得注意

    在JS中.是没有块级作用域的 举两个个样例: if语句块:     if (true){ var name='Ling'; } alert(name); 输出:Ling for语句块; for(var ...

  2. Spark Shuffle模块——Suffle Read过程分析

    在阅读本文之前.请先阅读Spark Sort Based Shuffle内存分析 Spark Shuffle Read调用栈例如以下: 1. org.apache.spark.rdd.Shuffled ...

  3. Cocos2d-x 3.2Lua演示样例UserDefaultTest(用户默认配置)

    Cocos2d-x 3.2演示样例UserDefaultTest(用户默认配置) 本篇博客介绍Cocos2d-x 3.2演示样例中的UserDefaulstTest,我们在开发中可能须要用到一些默认配 ...

  4. Linux CentOS完全卸载PHP

    很无语,CentOS居然php版本才5.1.6,很多开源的CMS无法安装. 查看php版本命令: #php -v 下面的命令是删除不干净的 #yum remove php 因为使用这个命令以后再用 # ...

  5. gunicorn syncworker 源码解析

    gunicorn支持不同的worker类型,同步或者异步,异步的话包括基于gevent.基于eventlet.基于Aiohttp(python版本需要大于3.3),也有多线程的版本.下面是gunico ...

  6. spring -boot s-tarter 详解

    Starter POMs是可以包含到应用中的一个方便的依赖关系描述符集合.你可以获取所有Spring及相关技术的一站式服务,而不需要翻阅示例代码,拷贝粘贴大量的依赖描述符.例如,如果你想使用Sprin ...

  7. 初探Azure的保留实例(Reserved Instance)

    最近的Ignite 2017宣布了Azure将在年底推出保留实例(Reserved Instance).虽然在没有RI的这些年,Azure的EA Monetary Commitment同样也提供了和R ...

  8. UITextFiled的输入框改成一条下划线

    在一些程序的界面中,它们的编辑框是一条线,而UITextFiled本身并没有这种style,所有需要我们自己设置.方法还是挺多的 第一种 , (1).我们可以声明一个类继承与UITextFiled ( ...

  9. Fiori缓存与它的清除

    最近在修改已有的Fiori应用,遇到了缓存上的一点问题,导致对Fiori应用的代码修改不能在前端页面生效.现将自己查到的一篇好资料翻译过来,以供参考.以下为正文. 2017.12.19更新:最近又遇到 ...

  10. 挑战App Store,微信通过“跳一跳”秀了一下“小程序”的肌肉

    2017年即将结束的时候,微信放了一个大招.随着最新的微信v6.6.1版本更新,基于小程序的"小游戏"板块正式上线.微信上首发的这款"小游戏"叫"跳一 ...