您的位置:首页 > 移动开发

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

2014-05-12 10:42 211 查看

写在前面



我到底是什么?

越界的可怕

做好自己

后记

  上一篇《恋爱虽易,相处不易:当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 == 0)
{
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 == 0)
{
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的用法,我们在:/article/4743808.html中有讲解,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参考文档:

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

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

【AutoMapper官方文档】DTO与Domin Model相互转换(下)
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: