结婚虽易,终老不易:EntityFramework和AutoMapper的婚后生活

写在前面

  • 我到底是什么?
  • 越界的可怕
  • 做好自己
  • 后记

  上一篇《恋爱虽易,相处不易:当EntityFramework爱上AutoMapper》文章的最后提到,虽然AutoMapper为了EntityFramework做了一些改变,然后就看似幸福的在一起了,但是有人(Roger Alsing)看不下去,写了一篇文章:http://rogeralsing.com/2013/12/01/why-mapping-dtos-to-entities-using-automapper-and-entityframework-is-horrible/,文中提到EntityFramework和AutoMapper在一起是可怕的,关于这篇英文文章,作为3.5级都考两次的我来说真是天书,无奈一边翻译一边猜测看了几遍,英文好的同学可以提供下中文版,因为文章中有代码,所以作者表达的意思还是可以理解一些。

  文章标题主要关键字:mapping DTOs to Entities,注意并不是“Entities to DTOs”,表示实体对象到DTO的转换,并做一些CURD操作,代码看似合理,但是好像又有些不合理,为什么?主要是对所涉及的概念不清晰,EntityFramework、AutoMapper和DTO,他们到底是什么?有没有在一起的可能?请接着往下看。

我到底是什么?

  EntityFramework(父亲)、AutoMapper(母亲)和DTO(孩子)你我都知道的官方定义:

  • EntityFramework:是微软以 ADO.NET 为基础所发展出来的对象关系对应 (O/R Mapping) 解决方案。
  • AutoMapper:Object-Object Mapping工具。
  • DTO:数据传输对象(Data Transfer Object)。

  EntityFramework的定义中有“ORM“关键字,那ORM又是什么?

ORM:对象关系映射(Object/Relation Mapping),是随着面向对象的软件开发方法发展而产生的。面向对象的开发方法是当今企业级应用开发环境中的主流开发方法,关系数据库是企业级应用环境中永久存放数据的主流数据存储系统。对象和关系数据是业务实体的两种表现形式,业务实体在内存中表现为对象,在数据库中表现为关系数据。内存中的对象之间存在关联和继承关系,而在数据库中,关系数据无法直接表达多对多关联和继承关系。   --百度百科

  概念清楚了就好办了,我们再来分析下,从上面定义可以看出:AutoMapper是Object-Object映射工具,EntityFramework是一种ORM框架,DTO是数据传输对象(Data Transfer Object),请注意这三个定义中都包含对象(Object)关键字,毫无疑问,AutoMapper所做的工作就是ORM中的“O”和DTO中的“O”之间的映射转换。  

  DTO中的“O”比较好理解,就是数据传输对象,不包含任何的业务逻辑,只是存储数据的一种对象,用于各层之间的数据传递。一般的项目都会采用分层设计(也就是常见的三层架构),每一层都是一个相对内聚的设计,一种松耦合结构。而层与层之间进行通讯的就是DTO,而这个“O”常常不是ORM的O。其实也可能不是DomainEntity,也不是ViewModel,但是它却有可能通过组合、分解等方式进行转换。

  那ORM中“O”是什么意思?关于ORM的使用,网上有很多的争论,抛开性能问题,有人提出说“ORM注定了业务逻辑和数据库的高度耦合”,我觉得这种理解是错误的。ORM的“O”是数据对象,与表等有一定的偶合,但它从架构设计上来说,只是仓储层的内聚设计,与业务逻辑无关(当然现在很多小系统会用它来直接替代业务逻辑对象),而真正的业务逻辑对象(按领域驱动设计来说)是领域对象,真正的的系统核心对象是领域对象,而数据对象是可变的,领域对象则相对稳定,数据对象到领域对象是通过仓储层的适配器来实现的。

  从上面的结论中可以看出,ORM中的“O”是数据对象。关于数据对象,有人说数据对象是稳定的,那要看数据是基于“数据”的设计,还是基于“对象”的设计。如果是基于“对象”的设计,那么设计之初,就必须把业务对象分析清楚、我们常把它说成领域对象,其实这个对象基本是稳定的(至少核心部分是稳定,如果核心对象变了,那是另一个话题,需求变了),而数据对象可能就不一定了,有可能SqlServer数据有的类型其它数据库没有,同样随着重构的进行,为了提高性能,可能会对一些表进行拆分和组合,原先在一个表中的数据,分成了两个表,或在视图中了。但不管数据表怎么变化,最终也只是仓储层内部的实现方式变了,当然ORM的“O”也会变,但出了这一层,一切都还是原来的样子。

  理解这些概念很重要,理解了你会发现,我为什么把EntityFramework看做“父亲”,AutoMapper看做“母亲”,DTO看做“孩子”,当然这只是某种意义上的关系比作,只有在这三者结合才会出现,比如DTO可以脱离EntityFramework和AutoMapper独立存在,但他就不是“孩子”的概念了。

越界的可怕

  什么叫越界?就是不是你干的事你却干了,所做的工作超出自己的范围之外,Roger Alsing的“Horrible”文章我觉得就是在表达这个意思。AutoMapper的工作范围只是对象之间的映射转换,也就是说EntityFramework中的“数据对象”到“DTO”之间的映射转换,但如果涉及到一些数据访问或是操作,这就是AutoMapper的越界行为,因为这些操作并不在她的职责范围之内,而应该是EntityFramework所做的工作。

  某种意义上,可以看做:AutoMapper(母亲)只是EntityFramework(父亲)和DTO(孩子)之间的桥梁或是沟通,至于赚钱养家的事就交给EntityFramework(父亲)去做,如果AutoMapper(母亲)帮助EntityFramework(父亲)去赚钱养家,可能会造成相反的效果,也就是说AutoMapper(母亲)请做好“全职太太”即可。

  我们来看下AutoMapper(母亲)的“越界行为”:

 1 public void CreateOrUpdateOrder(OrderDTO orderDTO)
 2 {
 3    var config = new ...
 4  
 5    //create an instanced mapper instead of the static API
 6    var mapper = new AutoMapper.MapperEngine(config);
 7    var context = ... //get some DbContext
 8       
 9    config.CreateMap<OrderDTO,Order>()
10    .ConstructUsing((OrderDTO orderDTO) =>
11    {
12       if (orderDTO.Id == 0)
13       {
14           var order = new Order();
15           context.OrderSet.Add(order);
16           return order;
17       }
18       return context.OrderSet.Include("Details").First(o => o.Id == orderDTO.Id);
19    })
20    //find out what details no longer exist in the DTO and delete the corresponding entities 
21    //from the dbcontext
22    .BeforeMap((dto, o) =>
23    {
24      o
25      .Details
26      .Where(d => !dto.Details.Any(ddto => ddto.Id == d.Id)).ToList()
27      .ForEach(deleted => context.DetailSet.Remove(deleted));
28    });
29  
30    config.CreateMap<DetailDTO, Detail>()
31    .ConstructUsing((DetailDTO detailDTO) =>
32    {
33        if (detailDTO.Id == 0)
34        {
35             var detail = new Detail();
36             context.DetailSet.Add(detail);
37             return detail;
38        }
39        return context.DetailSet.First(d => d.Id == detailDTO.Id);
40    });    
41   
42    mapper.Map<OrderDTO,Order>(orderDTO); 
43  
44    context.SaveChanges();
45 }    

  AutoMapper的ConstructUsing的用法,我们在:http://www.cnblogs.com/xishuai/p/3704435.html#xishuai_h1中有讲解,ConstructUsing表示自定义类型转换器,发生在映射之前,对映射的操作做一些处理并返回相应的目标类型,注意这里的处理并不是EntityFramework中的持久化,如果在AutoMapper的自定义类型转换器中做这些操作,就显得有点不伦不类了。

  关于AutoMapper这样的“越界行为”,Roger Alsing总结出了其中的优缺点,本人就不翻译了,以免起到误读的效果。

  优点:

  • Looks simple on paper
  • Easy to implement on read side and client side

  缺点:

  • Bloody horrible to implement on the write side, and gets even worse the larger the DTO is
  • Relies on magic names if using AutoMapper
  • Network ineffective if dealing with large DTOs
  • You lose semantics and intentions, there is no way to know WHY a change happened

做好自己

  如果我们在上面代码中去掉AutoMapper,将会变得如何?请看下面:

 1 public void CreateOrUpdateOrder(OrderDTO orderDTO)
 2 {
 3     var ctx = ... //get some DbContext
 4     var order = ctx.OrderSet.FirstOrDefault(o => o.Id == orderDTO.Id);
 5     if (order == null)
 6     {
 7         order = new Order();
 8         ctx.OrderSet.Add(order);
 9     }
10  
11     //Map properties
12     order.Address = orderDTO.Address;            
13  
14     //remove deleted details
15     order.Details
16     .Where(d => !orderDTO.Details.Any(detailDTO => detailDTO.Id == d.Id))
17     .Each(deleted => ctx.DetailSet.Remove(deleted));
18  
19     //update or add details
20     orderDTO.Details.Each(detailDTO =>
21     {
22         var detail = order.Details.FirstOrDefault(d => d.Id == detailDTO.Id);
23         if (detail == null)
24         {
25             detail = new Detail();
26             order.Details.Add(detail);
27         }
28         detail.Name = detailDTO.Name;
29         detail.Quantity = detailDTO.Quantity;
30     });
31  
32    context.SaveChanges();
33 }   

  这样代码更加清洁,与使用AutoMapper的代码形成了明显的对比,但如果去掉AutoMapper也就失去了DTO的意义,试想没有母亲哪来的孩子?但是如果EntityFramework(父亲)、AutoMapper(母亲)和DTO(孩子)这三口之家想和谐的生活在一起,那怎么办?就是AutoMapper只要负责对象映射转换即可,也就是做EntityFramework(父亲)和DTO(孩子)之间的“沟通桥梁”,也就是“全职太太”:

 1    Mapper.CreateMap<OrderDTO, Order>();
 2    Mapper.CreateMap<DetailDTO, Detail>();
 3    using (var context = new OrderContext())
 4    {
 5        var existOrder = context.Orders.FirstOrDefault(order => order.Id == orderDTO.Id);
 6        if (existOrder == null)
 7        {
 8            var order = Mapper.Map<OrderDTO, Order>(orderDTO);
 9            context.Orders.Add(order);
10            context.Details.AddRange(order.Details);
11            context.SaveChanges();
12        }
13    }

后记

  示例代码下载:http://pan.baidu.com/s/1o6EzFq6

  做了“全职太太”的AutoMapper,就这样和EntityFramework幸福的生活下去了,看到这有人又可能有疑问,上篇中AutoMapper为EntityFramework做的IQueryable扩展是不是“越界行为”,注意QueryableExtensions只是AutoMapper所做的扩展,并不是代替EntityFramework去完成持久化操作。

  如果你觉得EntityFramework和AutoMapper可以幸福终老,那就疯狂的“戳”右下角的“推荐”吧。^_^

  AutoMapper参考文档:

  • 【AutoMapper官方文档】DTO与Domin Model相互转换(上)
  • 【AutoMapper官方文档】DTO与Domin Model相互转换(中)
  • 【AutoMapper官方文档】DTO与Domin Model相互转换(下)

转载于:https://www.cnblogs.com/xishuai/p/3720662.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/575470.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Linux 学会看日志文件处理问题

rsyslog是一个进程&#xff0c;是一个命令。管理日志的。–》系统日志记录器 它有一个配置文件&#xff1a;/etc/rsyslog.conf 自己创建日志时&#xff0c;要修改该配置文件。 日志的作用&#xff1a; 用于记录系统、程序运行中发生的各种事件&#xff1b; 通过阅读日志&…

html中input、label、form、textarea、select

表单input&#xff1a; 表单是用来收集信息的&#xff0c;由表单控件(表单元素)、提示信息、表单域构成。 input控件的属性及值&#xff1a; 除以上属性外&#xff0c;input元素type属性还有一个number属性值&#xff0c;此为仅可以填数字&#xff0c;默认是可以选择或者填写…

mac os 显示文件列表命令 ls -a

显示正常文件列表用ls就行了&#xff0c;但是要是想显示隐藏的文件&#xff0c;需要加-a转载于:https://www.cnblogs.com/coolbear/p/3723088.html

html中文本格式化、预格式化、计算机输出标签、address、title、文字方向、著作

文本格式化标签&#xff1a; 预格式化标签pre&#xff1a; 网页中文本默认是显示一行&#xff0c;没有格式&#xff0c;通过<pre></pre>标签包裹会显示默认的格式。 <pre> <!-- 通过pre标签包裹后&#xff0c;会有默认的格式 -->云想衣裳花想容&…

Outlook 与 Outlook Express 的区别

Outlook 与 Outlook Express 的区别是什么? 这两个是两种软件&#xff0c;不要因为同样都有“Outlook”的字样、都可以收发Email&#xff0c;就觉得他们差不多。Outlook Express是一个新闻、邮件软件&#xff0c;属于操作系统自带的组件&#xff0c;附属于操作系统。而Outlook…

配置札记

Spring可以通过指定classpath*:与classpath:前缀加路径的方式从classpath加载文件,如bean的定义文件.classpath*:的出现是为了从多个jar文件中加载相同的文件.classpath:只能加载找到的第一个文件.转载于:https://www.cnblogs.com/angelshelter/p/3725862.html

html5简介、选项输入框、表单元素分组、input新增属性及属性值

HTML5简介: 定义&#xff1a;HTML5号称下一代HTML&#xff0c;html的最新版本&#xff0c;定义了新的标签、css、JavaScript&#xff0c;html5新标签IE9以上版本浏览器才兼容&#xff0c;因此在实际开发中要问老板是否兼容低版本浏览器。 扩展内容&#xff1a;语义化标签、本…

SP2010开发和VS2010专家食谱--第六章节--Web Services和REST(5)--Inserting new contacts through REST...

我们现在知道了我们可以使用REST请求从SharePoint列表获得数据&#xff0c;如何从客户端应用程序添加数据到列表呢&#xff1f;本文中&#xff0c;我们将探讨如何做到。 转载于:https://www.cnblogs.com/crazygolf/p/3856779.html

Linux crontab 定时任务没执行,没收到错误信息邮件

crond 定时任务 没执行&#xff0c;简单的打印日期&#xff0c;reboot 命令 等也没执行成功&#xff08;语法确保没错&#xff09;。捣鼓一整算是有些进展。 centos7 不过这个好像没啥问题吧。。 分割线------------------------------------------------------- 01 最开始就…

Chapter 12 外观模式

外观模式&#xff1a;为子系统中的一组接口提供一个一致的界面&#xff0c;此模式定义了一个高层接口&#xff0c;这个模式使得这一子系统更加容易使用。 外观模式三个阶段&#xff1a; 首先&#xff0c;在设计初期阶段&#xff0c;应该要有意识的将不同的两个层分离。 其次&am…

$@ $# $2 $0 $* Linux 参数使用

命令行参数 运行脚本时传递给脚本的参数称为命令行参数。命令行参数用 $n 表示&#xff0c;例如&#xff0c;$1 表示第一个参数&#xff0c;$2 表示第二个参数&#xff0c;依次类推。 1 $ 表示所有参数&#xff1b;并且所有参数都是独立的&#xff1b;可以用来做 for e…

html5中音频、视频标签、自定义播放器常用属性及方法、全屏操作、新增属性兼容问题

多媒体标签: 音频标签audio: <audio src"音频文件的URL"></audio><!-- audio标签需要controls控件才可以播放音频&#xff0c;controls的属性值可以省略&#xff0c;如果URL为视频格式文件&#xff0c;则只会播放音频 -->html5中通过audio标签实现…

设计模式(17) 访问者模式(VISITOR) C++实现

意图&#xff1a; 表示一个作用于某对象结构的各元素的操作。它使你可以再不改变各元素的类的前提下定义作用于这些元素的新操作。 动机&#xff1a; 之前在学校的最后一个小项目就是做一个编译器&#xff0c;当时使用的就是访问者模式。 在静态分析阶段&#xff0c;将源程序表…

云服务器的主机名是否可以修改??

云服务器的主机名是否可以修改&#xff1f; 1 要是自己建的虚拟机等&#xff0c;这可以随便改&#xff0c;不影响。 2 要是云服务器&#xff0c;阿里云 华为云等&#xff0c;因为是项目上服务器厂商给提供的服务器信息&#xff0c;所以遇到问题需要改主机名时&#xff0c;为了…

CSS简介、行内样式、内部样式、外部样式、注释、引入其他CSS文件

CSS的发展历程&#xff1a; 起初是没有css的&#xff0c;只有少量样式是可以写在html标签中&#xff0c;这样代码格外显得臃肿&#xff0c;此时CSS就出现了。 初识CSS&#xff1a; CSS(Cascading Style Sheets),被称为CSS样式表或者层叠样式表&#xff0c;主要用于设置HTML页…

CISCO PVST+配置和结果验证 per vlan spanning tree(51cto 实验10)

1. 实验线路连接图使用Cisco Packet Tracer6.0 构建拓扑结构图 2. 实验内容(1) 参阅教材中内容&#xff0c;完成PVST的配置内容。(2) 在各台交换机上使用show spanning-tree vlan 10 和show spanning-tree vlan 20 命令&#xff0c;查看根桥信息、各端口角色和各端口状态。(3) …

Linux crond 每分钟,每小时,每天,每周,每月,每年 的表达式写法

每分钟执行 * * * * * 每五分钟执行 */5 * * * * 每小时执行(让分钟取0) 0 * * * * 每2小时执行 0 */2 * * * 每天执行 0 0 * * * 每周执行 0 0 * * 0 每月执行 0 0 1 * * 每年执行 0 0 1 1 *

css中选择器介绍

选择器&#xff1a; 选择器是用来选择目标元素的&#xff0c;选择器分基础选择器和复合选择器及伪类选择器。 基础选择器&#xff1a; 标签名{} /* 1 标签选择器&#xff1a;把某类标签全部选中&#xff0c;如下&#xff1a; */p{font-size:36px;}.类名{} /* 2 类选择器(clas…

使用iometer测试

对国产机进行测试 1.win7上安装测试 下载&#xff1a; 点击打开链接 双击安装即可。 2.ubuntu下配置&#xff1a; OS: Ubuntu 12.04LTS x86_64Kernel: 3.5.0-26-generic下载&#xff1a; 点击打开链接编译安装 unzip iometer-2006_07_27.common-src.zip\?download cd iometer…

http://mirrors.aliyuncs.com/centos/7/extras/x86_64/repodata/repomd.xml: [Errno 12] Timeout on http:/

对于这个点&#xff0c;我的情况是【errno12】&#xff0c; 众多帖子里还是请求404错误居多&#xff0c;情况不一样。我的安装阿里yum配置过程没问题&#xff0c;然后想起之前有过这样&#xff0c;怀疑是dns解析不到mirrors.aliyuncs.com这种地址&#xff0c;所以在dns文件里加…