zoukankan      html  css  js  c++  java
  • 浴室沉思:聊聊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,这个问题我们以后有时间再说。

  • 相关阅读:
    论文阅读笔记(七十二)【ICMR2020】:Compact Network Training for Person ReID
    论文阅读笔记(七十一)【CVPR2018】:Harmonious Attention Network for Person Re-Identification
    论文阅读笔记(七十)【CVPR2021】:Combined Depth Space based Architecture Search For Person Re-identification
    论文阅读笔记(六十九)【CVPR2021】:BiCnet-TKS: Learning Efficient Spatial-Temporal Representation for Video Person Re-Identification
    论文阅读笔记(六十八):图文跨模态行人检索(3篇)
    (一万小时计划)二月二日总结
    (一万小时计划)一月二十三日总结
    (一万小时计划)一月二十日总结
    (一万小时计划)一月二日总结
    (一万小时计划)十二月二十二日总结
  • 原文地址:https://www.cnblogs.com/Mutuduxf/p/8276425.html
Copyright © 2011-2022 走看看