写在前面

  上一篇《恋爱虽易,相处不易:当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(母亲)的“越界行为”:

 public void CreateOrUpdateOrder(OrderDTO orderDTO)
{
var config = new ... //create an instanced mapper instead of the static API
var mapper = new AutoMapper.MapperEngine(config);
var context = ... //get some DbContext config.CreateMap<OrderDTO,Order>()
.ConstructUsing((OrderDTO orderDTO) =>
{
if (orderDTO.Id == )
{
var order = new Order();
context.OrderSet.Add(order);
return order;
}
return context.OrderSet.Include("Details").First(o => o.Id == orderDTO.Id);
})
//find out what details no longer exist in the DTO and delete the corresponding entities
//from the dbcontext
.BeforeMap((dto, o) =>
{
o
.Details
.Where(d => !dto.Details.Any(ddto => ddto.Id == d.Id)).ToList()
.ForEach(deleted => context.DetailSet.Remove(deleted));
}); config.CreateMap<DetailDTO, Detail>()
.ConstructUsing((DetailDTO detailDTO) =>
{
if (detailDTO.Id == )
{
var detail = new Detail();
context.DetailSet.Add(detail);
return detail;
}
return context.DetailSet.First(d => d.Id == detailDTO.Id);
}); mapper.Map<OrderDTO,Order>(orderDTO); context.SaveChanges();
}

  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,将会变得如何?请看下面:

 public void CreateOrUpdateOrder(OrderDTO orderDTO)
{
var ctx = ... //get some DbContext
var order = ctx.OrderSet.FirstOrDefault(o => o.Id == orderDTO.Id);
if (order == null)
{
order = new Order();
ctx.OrderSet.Add(order);
} //Map properties
order.Address = orderDTO.Address; //remove deleted details
order.Details
.Where(d => !orderDTO.Details.Any(detailDTO => detailDTO.Id == d.Id))
.Each(deleted => ctx.DetailSet.Remove(deleted)); //update or add details
orderDTO.Details.Each(detailDTO =>
{
var detail = order.Details.FirstOrDefault(d => d.Id == detailDTO.Id);
if (detail == null)
{
detail = new Detail();
order.Details.Add(detail);
}
detail.Name = detailDTO.Name;
detail.Quantity = detailDTO.Quantity;
}); context.SaveChanges();
}

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

    Mapper.CreateMap<OrderDTO, Order>();
Mapper.CreateMap<DetailDTO, Detail>();
using (var context = new OrderContext())
{
var existOrder = context.Orders.FirstOrDefault(order => order.Id == orderDTO.Id);
if (existOrder == null)
{
var order = Mapper.Map<OrderDTO, Order>(orderDTO);
context.Orders.Add(order);
context.Details.AddRange(order.Details);
context.SaveChanges();
}
}

后记

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

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

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

  AutoMapper参考文档:

结婚虽易,终老不易:EntityFramework和AutoMapper的婚后生活的更多相关文章

  1. DTO学习系列之AutoMapper(六)----EntityFramework和AutoMapper的婚后生活

    写在前面 我到底是什么? 越界的可怕 做好自己 后记 文章标题主要关键字:mapping DTOs to Entities,注意并不是“Entities to DTOs”,表示实体对象到DTO的转换, ...

  2. 恋爱虽易,相处不易:当EntityFramework爱上AutoMapper

    剧情开始 为何相爱? 相处的问题? 女人的伟大? 剧情收尾? 有时候相识即是一种缘分,相爱也不需要太多的理由,一个眼神足矣,当EntityFramework遇上AutoMapper,就是如此,恋爱虽易 ...

  3. DTO学习系列之AutoMapper(五)----当EntityFramework爱上AutoMapper

    有时候相识即是一种缘分,相爱也不需要太多的理由,一个眼神足矣,当EntityFramework遇上AutoMapper,就是如此,恋爱虽易,相处不易. 在DDD(领域驱动设计)中,使用AutoMapp ...

  4. 当EntityFramework爱上AutoMapper

    有时候相识即是一种缘分,相爱也不需要太多的理由,一个眼神足矣,当EntityFramework遇上AutoMapper,就是如此,恋爱虽易,相处不易. 在DDD(领域驱动设计)中,使用AutoMapp ...

  5. 使用Adminlite + ASP.NET MVC5(C#) + Entityframework + AutoFac + AutoMapper写了个api接口文档管理系统

    一.演示: 接口查看:http://apidoc.docode.top/ 接口后台:http://apiadmin.docode.top/ 登录:administrator,123456 二.使用到的 ...

  6. 贫血模型;DTO:数据传输对象(Data Transfer Object);AutoMapper ;Domain Model(领域模型);DDD(领域驱动设计)

    ====================== 我自己的理解 ========================== 一:  DTO  我自己的理解,就是 比如你有一个类,跟数据库的table表结构一模一 ...

  7. 爱与恨的抉择:ASP.NET 5+EntityFramework 7

    EF7 的纠缠 ASP.NET 5 的无助 忘不了你的好 一开始列出的这个博文大纲,让我想到了很久之前的一篇博文:恋爱虽易,相处不易:当EntityFramework爱上AutoMapper,只不过这 ...

  8. ASP.NET 5+EntityFramework 7

    爱与恨的抉择:ASP.NET 5+EntityFramework 7   EF7 的纠缠 ASP.NET 5 的无助 忘不了你的好 一开始列出的这个博文大纲,让我想到了很久之前的一篇博文:恋爱虽易,相 ...

  9. 一次修改闭源 Entity Provider 程序集以兼容新 EntityFramework 的过程

    读完本文你会知道,如何在没有源码的情况下,直接修改一个 DLL 以去除 DLL 上的强命名限制,并在该程序集上直接添加你的“友元程序集(一种特殊的 Attribute,将它应用在程序集上,使得程序集内 ...

随机推荐

  1. 【Silverlight】打开Silverlight程序报错,"未找到导入的项目......请确认<Import>声明中的路径正确,且磁盘上存在该文件"

    在打开Silverlight程序时,报错(如图所示),程序使用的是Visual Studio 2013和最新的Silverlight版本(Silverlight5). 然后我在网上找了下说:Silve ...

  2. Flask 重新认识

    总是觉的学习东西有点猴子掰玉米的感觉.今天就重新再掰一次吧. Installation: 安装之前建议先安装virtualenv,这个东东是帮助你在多个python版本之间保持同步,不至于python ...

  3. *CF2.D(哥德巴赫猜想)

    D. Taxes time limit per test 2 seconds memory limit per test 256 megabytes input standard input outp ...

  4. Ibatis 使用心得

    (1)建表语句 sqlMap: <statement id="createTable" parameterClass="String" > $sql ...

  5. 关于DOM的一些笔记(二)

    1.选择符API (1).querySelector()方法 querySelector()方法接受一个CSS选择符,返回与该模式匹配的第一个元素,如果没有找到匹配的元素,返回null. 通过Docu ...

  6. 如何用js得到当前页面的url信息方法(JS获取当前网址信息)

    设置或获取对象指定的文件名或路径. alert(window.location.pathname) 设置或获取整个 URL 为字符串. alert(window.location.href); 设置或 ...

  7. Collections+Iterator 接口 | Map+HashMap+HashTable+TreeMap |

    Collections+Iterator 接口 1. Collections 是一个操作 Set.List 和 Map 等集合的工具类 Collections 中提供了大量方法对集合元素进行排序.查询 ...

  8. SQL Server最近怎样了

    SQL Server最近怎样了 又到年终了,大家都作最后冲刺 最近园子里真的多了很多口水帖,无论大家争论得多么激烈,时间依然滴答滴答地过,争论完之后我们依然要继续埋头苦干 为年终奖.为明年做准备 这里 ...

  9. 让Response.Redirect页面重定向更有效率

    用 Redirect 方法可将浏览器重定向到另一个 URL,而不是将内容发送给用户. 这里有一篇文章介绍使用Redirect<Using Response.Redirect Effectivel ...

  10. Key/Value之王Memcached初探:一、掀起Memcached的盖头来

    一.Memcached是何方神圣? 在数据驱动的Web开发中,经常要重复从数据库中取出相同的数据,这种重复极大的增加了数据库负载.缓存是解决这个问题的好办法.但是ASP.NET中的HttpRuntim ...