前言:前篇搭建了下WCF的代码,就提到了DTO的概念,对于为什么要有这么一个DTO的对象,上章可能对于这点不太详尽,在此不厌其烦再来提提它的作用:

  • 从安全上面考虑,领域Model都带有领域业务,让Client端引用Domain Model就意味着Client端可以绕过应用层直接完成业务逻辑的调用,这样是一种不安全的机制。
  • 从对象传递效率上面考虑,领域Model带有业务,而这些业务一般对于UI层是没有意义的,所以带有业务的model传递起来会加重网络负担。
  • 网上还说了DTOmodel最大的意义在于跨平台,Domain Model都是与特定的语言的数据类型有关,而这些数据类型是不能跨平台的,比如Java的类型就不能被C#使用。但在分布式模式下,Client端与Server端的平台不同是很正常的,如果Service直接返回Domain Model,Client端根本无法解析,这就要求Service返回的结果必须是标准的格式字节流。让Domain Model只使用简单类型(字符和数值)?让数据类型约束Domain Model显然不是一个好想法,所以DTO似乎是必不可少的了。

既然我们要使用DTO,那么有一件事我们就非做不可了,我们从领域层得到的是领域Model,如何把领域Model转换成只带有数据属性的DTO传递到前台呢?又或者我们从前台提交一个DTO对象,如何将DTO转换成领域Model而提交到后台呢?这个时候就需要我们的对象映射工具,目前市面上对象映射工具较多,但博主最熟悉的还是Automapper,这章就来分享下Automapper的使用。

DDD领域驱动设计初探系列文章:

一、AutoMapper

Automapper是一个object-object mapping(对象映射)工具,一般主要用于两个对象之间数据映射和交换。当然你也可以自己通过反射去写对象的映射,对于简单的两个属性间的数据转换,肯定没什么问题。但是如果遇到某些复杂的数据转换,比如指定某一个对象的某个属性映射到另一个对象的某一个属性,这种情况如果我们自己手动映射,恐怕就有点麻烦了吧。既然我们有现成的工具,为什么不用呢?

二、AutoMapper引用到项目中

向项目中添加AutoMapper的引用有两种方式:

1、Nuget方式

在需要使用AutoMapper的项目文件上面右键→管理Nuget程序包,打开Nuget界面,搜索Automapper,然后安装第一个即可。如下图:

2、程序包管理控制台方式

点击Visual Studio的工具菜单→程序包管理控制台,然后选择需要安装Automapper的项目(下图中的默认项目),最后在控制台里面输入命令“Install-Package AutoMapper”命令即可按照Automapper包:

三、AutoMapper使用代码示例

1、最简单的对象映射

AutoMapper使用起来还是比较简单的,最简单的用法你只需要两句话:

  1. var oMenu = new TB_MENU() { MENU_NAME="权限管理", MENU_LEVEL="" };
    Mapper.CreateMap<TB_MENU, DTO_TB_MENU>();
  2. var oDto = Mapper.Map<DTO_TB_MENU>(oMenu);

首先创建映射,然后传入需要映射的对象执行映射。相信在项目中使用过AutoMapper的原因肯定也写过类似这样的AutoMapperHelper

  1. /// <summary>
  2. /// AutoMapper帮助类
  3. /// </summary>
  4. public static class AutoMapperHelper
  5. {
  6. /// <summary>
  7. /// 单个对象映射
  8. /// </summary>
  9. public static T MapTo<T>(this object obj)
  10. {
  11. if (obj == null) return default(T);
  12. Mapper.CreateMap(obj.GetType(), typeof(T));
  13. return Mapper.Map<T>(obj);
  14. }
  15.  
  16. /// <summary>
  17. /// 集合列表类型映射
  18. /// </summary>
  19. public static List<TDestination> MapToList<TSource, TDestination>(this IEnumerable<TSource> source)
  20. {
  21. Mapper.CreateMap<TSource, TDestination>();
  22. return Mapper.Map<List<TDestination>>(source);
  23. }
  24. }

当然,这是最简单的用法,稍微复杂点的用法我们在后面慢慢介绍。

2、指定字段的对象映射

前面说了,对于指定某一个对象的某个属性映射到另一个对象的某一个属性,这种场景,我们先来看看下面代码:

  1.    public partial class TB_USERS : BaseEntity
  2. {
  3. public string USER_ID { get; set; }
  4. public string USER_NAME { get; set; }
  5. public string USER_PASSWORD { get; set; }
  6. public string FULLNAME { get; set; }
  7. public string DEPARTMENT_ID { get; set; }
  8.  
  9.      public virtual TB_DEPARTMENT TB_DEPARTMENT { get; set; }
  10.      //...后面肯定还有其他领域行为
  11. }
  1.    public partial class TB_DEPARTMENT : BaseEntity
  2. {
  3. public string DEPARTMENT_ID { get; set; }
  4. public string NAME { get; set; }
  5. }

领域层有这两个实体model,然后我们需要得到下面的DTO_TB_USERS这一个对象

  1.    public class DTO_TB_USERS
  2. {
  3. [DataMember]
  4. public string USER_ID { get; set; }
  5.  
  6. [DataMember]
  7. public string USER_NAME { get; set; }
  8.  
  9. [DataMember]
  10. public string USER_PASSWORD { get; set; }
  11.  
  12. [DataMember]
  13. public string FULLNAME { get; set; }
  14.  
  15. [DataMember]
  16. public string DEPARTMENT_ID { get; set; }
  17.  
  18.      [DataMember]
         public string DEPARTMENT_NAME { get; set; }
  19. }

这个时候DTO_TB_USERS这个对象的属性分布在其他两个领域实体里面,我们看看AutoMapper如何解决:

  1. var oDomainUser = userRepository.Entities.FirstOrDefault();
  2. var map = Mapper.CreateMap<TB_USERS, DTO_TB_USERS>();
  3. map.ForMember(d => d.DEPARTMENT_NAME, opt => opt.MapFrom(x => x.TB_DEPARTMENT.NAME));
  4. var oDto = Mapper.Map<TB_USERS, DTO_TB_USERS>(oDomainUser);

通过上面的代码,ForMember()方法会指定哪个字段转换为哪个字段,这样就完美的将对象的层级结构由二级变成了一级(即将TB_USERS下面TB_DEPARTMENT对象的NAME值转换成了DTO_TB_USERS的DEPARTMENT_NAME值)。除此之外,Automapper里面还可以通过ForMember帮我们做其他很多我们想不到的事情,比如可以设置某个属性值保留初始值,只需要通过

  1. map.ForMember(d => d.DEPARTMENT_NAME, opt => opt.Ignore());

这一句就帮我们搞定。

3、传递lamada的表达式映射

还记得我们在仓储里面封装了传递lamada表达式的查询方法么?试想,如果我们在Web层里面也希望传递lamada表达式去后台查询,那么这个时候就有点问题了,因为我们Web里面只能访问DTO的Model,所以只能传入DTO Model的lamada,而我们仓储里面需要传入的是领域Model的lamada,那么问题就来了,这两个lamada表达式之间必须存在一个转换关系,试想,这些东西如果让我们手动去处理,还是有难度的吧!还好,我们神奇的Automapper替我们想到了。它能够帮我们将DTO的lamada转换成领域Model的lamada,来看看代码吧:

  1.      [Import]
  2.      public IUserRepository userRepository { get; set; }
  3.  
  4.      public virtual IList<DTO> Find(Expression<Func<DTO, bool>> selector)
  5. {
  6. //得到从Web传过来和DTOModel相关的lamaba表达式的委托
  7. Func<DTO, bool> match = selector.Compile();
  8. //创建映射Expression的委托
  9. Func<T, DTO> mapper = AutoMapper.QueryableExtensions.Extensions.CreateMapExpression<T, DTO>(Mapper.Engine).Compile();
  10. //得到领域Model相关的lamada
  11. Expression<Func<T, bool>> lamada = ef_t => match(mapper(ef_t));
  12. List<T> list = userRepository.Find(lamada).ToList();
  13. return Mapper.Map<List<T>, List<DTO>>(list);
  14. }

上面方法完美实现了两种lamada之间的转换,但根据博主的使用经历,这种转换对属性的类型有很严格的要求,必须保证领域model和DTO的Model同一个属性的类型完全相同,否则容易报异常。使用的时候需要注意。实际使用的方法:

  1. public List<DtoModel> GetDtoByLamada<DtoModel,DomainModel>(IRepository<DomainModel> oRepository, Expression<Func<DtoModel, bool>> selector = null)
  2. where DomainModel : AggregateRoot
  3. where DtoModel : DTO_BASEMODEL
  4. {
  5. if (selector == null)
  6. {
  7. var lstDomainModel = oRepository.Entities.ToList();
  8. return Mapper.Map<List<DomainModel>, List<DtoModel>>(lstDomainModel);
  9. }
  10. //得到从Web传过来和DTOModel相关的lamaba表达式的委托
  11. Mapper.CreateMap<DtoModel, DomainModel>();
  12. Mapper.CreateMap<DomainModel, DtoModel>();
  13. Func<DtoModel, bool> match = selector.Compile();
  14. //创建映射Expression的委托
  15. Func<DomainModel, DtoModel> mapper = AutoMapper.QueryableExtensions.Extensions.CreateMapExpression<DomainModel, DtoModel>(Mapper.Engine).Compile();
  16. //得到领域Model相关的lamada
  17. Expression<Func<DomainModel, bool>> lamada = ef_t => match(mapper(ef_t));
  18. List<DomainModel> list = oRepository.Find(lamada).ToList();
  19. return Mapper.Map<List<DomainModel>, List<DtoModel>>(list);
  20. }

调用

  1. public class PowerManageWCFService :BaseService, IPowerManageWCFService
  2. {
  3. #region Fields
  4. [Import]
  5. private IUserRepository userRepository { get; set; }
  6.  
  7. [Import]
  8. private IDepartmentRepository departmentRepository { get; set; }
  9.  
  10. [Import]
  11. private IRoleRepository roleRepository { get; set; }
  12.  
  13. [Import]
  14. private IMenuRepository menuRepository { get; set; }
  15. #endregion
  16.  
  17. #region Constust
  18. public PowerManageWCFService()
  19. {
  20. //注册MEF
  21. Regisgter.regisgter().ComposeParts(this);
  22. }
  23. #endregion
  24.  
  25. #region WCF服务接口实现
  26. public List<DTO_TB_USERS> GetUsers(Expression<Func<DTO_TB_USERS, bool>> selector)
  27. {
  28. return base.GetDtoByLamada<DTO_TB_USERS, TB_USERS>(userRepository, selector);
  29. }
  30.  
  31. public List<DTO_TB_DEPARTMENT> GetDepartments(Expression<Func<DTO_TB_DEPARTMENT, bool>> selector)
  32. {
  33. return base.GetDtoByLamada<DTO_TB_DEPARTMENT, TB_DEPARTMENT>(departmentRepository, selector);
  34. }
  35.  
  36. public List<DTO_TB_ROLE> GetRoles(Expression<Func<DTO_TB_ROLE, bool>> selector)
  37. {
  38. return base.GetDtoByLamada<DTO_TB_ROLE, TB_ROLE>(roleRepository, selector);
  39. }
  40.  
  41. public List<DTO_TB_MENU> GetMenus(Expression<Func<DTO_TB_MENU, bool>> selector)
  42. {
  43. return base.GetDtoByLamada<DTO_TB_MENU, TB_MENU>(menuRepository, selector);
  44. }
  45. #endregion
  46. }

4、Automapper的其他应用

除了上面介绍的Automapper的几个简单使用,其他还有其他的一些用法。

网上很多介绍DataReader对象和实体类之间的映射:

  1. using (IDataReader reader = db.ExecuteReader(command))
  2. {
  3. if (reader.Read())
  4. {
  5. return AutoMapper.Mapper.DynamicMap<Product>(reader);
  6. }
  7. }

至此,AutoMapper的常见用法基本分享完了,至于更高级的用法,有兴趣可以看看蟋蟀兄的【AutoMapper官方文档】DTO与Domin Model相互转换(上)。虽然很多高级用法在实际项目中很难用上,但多了解一点似乎也并没有坏处。

C#进阶系列——DDD领域驱动设计初探(五):AutoMapper使用的更多相关文章

  1. C#进阶系列——DDD领域驱动设计初探(一):聚合

    前言:又有差不多半个月没写点什么了,感觉这样很对不起自己似的.今天看到一篇博文里面写道:越是忙人越有时间写博客.呵呵,似乎有点道理,博主为了证明自己也是忙人,这不就来学习下DDD这么一个听上去高大上的 ...

  2. C#进阶系列——DDD领域驱动设计初探(二):仓储Repository(上)

    前言:上篇介绍了DDD设计Demo里面的聚合划分以及实体和聚合根的设计,这章继续来说说DDD里面最具争议的话题之一的仓储Repository,为什么Repository会有这么大的争议,博主认为主要原 ...

  3. C#进阶系列——DDD领域驱动设计初探(三):仓储Repository(下)

    前言:上篇介绍了下仓储的代码架构示例以及简单分析了仓储了使用优势.本章还是继续来完善下仓储的设计.上章说了,仓储的最主要作用的分离领域层和具体的技术架构,使得领域层更加专注领域逻辑.那么涉及到具体的实 ...

  4. C#进阶系列——DDD领域驱动设计初探(四):WCF搭建

    前言:前面三篇分享了下DDD里面的两个主要特性:聚合和仓储.领域层的搭建基本完成,当然还涉及到领域事件和领域服务的部分,后面再项目搭建的过程中慢慢引入,博主的思路是先将整个架构走通,然后一步一步来添加 ...

  5. C#进阶系列——DDD领域驱动设计初探(六):领域服务

    前言:之前一直在搭建项目架构的代码,有点偏离我们的主题(DDD)了,这篇我们继续来聊聊DDD里面另一个比较重要的知识点:领域服务.关于领域服务的使用,书中也介绍得比较晦涩,在此就根据博主自己的理解谈谈 ...

  6. C#进阶系列——DDD领域驱动设计初探(七):Web层的搭建

    前言:好久没更新博客了,每天被该死的业务缠身,今天正好一个模块完成了,继续来完善我们的代码.之前的六篇完成了领域层.应用层.以及基础结构层的部分代码,这篇打算搭建下UI层的代码. DDD领域驱动设计初 ...

  7. DDD领域驱动设计初探(七):Web层的搭建

    前言:好久没更新博客了,每天被该死的业务缠身,今天正好一个模块完成了,继续来完善我们的代码.之前的六篇完成了领域层.应用层.以及基础结构层的部分代码,这篇打算搭建下UI层的代码. DDD领域驱动设计初 ...

  8. DDD领域驱动设计初探(六):领域服务

    前言:之前一直在搭建项目架构的代码,有点偏离我们的主题(DDD)了,这篇我们继续来聊聊DDD里面另一个比较重要的知识点:领域服务.关于领域服务的使用,书中也介绍得比较晦涩,在此就根据博主自己的理解谈谈 ...

  9. DDD领域驱动设计初探(五):AutoMapper使用

    前言:前篇搭建了下WCF的代码,就提到了DTO的概念,对于为什么要有这么一个DTO的对象,上章可能对于这点不太详尽,在此不厌其烦再来提提它的作用: 从安全上面考虑,领域Model都带有领域业务,让Cl ...

随机推荐

  1. div盒子垂直水平居中

    div盒子,水平垂直居中. <!DOCTYPE html><html> <head> <meta charset="utf-8"> ...

  2. MFC--响应鼠标和键盘操作

    一个程序最重要的部分之一是对鼠标和键盘操作的响应. 一.  理解鼠标事件.之前对鼠标事件的认识仅仅局限于处理控件的单击与双击事件.但实际鼠标的操作包含很多.这里将以一个画图的小程序讲解对鼠标的响应. ...

  3. HTML5 获取地理位置信息

    HTML5增加的新功能,获取地理位置信息,如果浏览器支持且设备有定位功能,就能够直接使用这组API来获取当前信息位置.该Geolocation API可以应用于移动设备中的地理位置. Geolocat ...

  4. 奇妙的CSS之布局与定位

    前言 关于布局与定位是Web前端开发里非常基础而又重要的部分.介绍相关知识的文章,很容易就可以找到.虽然,这方面的知识点不是很多,但我们如果不弄清楚,在运用时候往往会出现预料之外的布局,这些“意外”有 ...

  5. iOS - 详细理解KVC与KVO

    详细理解KVC与KVO 在面试的时候,KVC与KVO有些时候还是会问到的,并且他们都是Objective C的关键概念,在这里我们先做一个简单地介绍: (一)KVC: KVC即指:NSKeyValue ...

  6. 最新深度技术GHOST XP系统旗舰增强版 V2016年

    来自系统妈:http://www.xitongma.com 深度技术GHOST xp系统旗舰增强版 V2016年 系统概述 深度技术ghost xp系统旗舰增强版集合微软JAVA虚拟机IE插件,增强浏 ...

  7. IOS 日期的简洁格式展示

    首先我要解释一下标题的意义,日期的简洁格式展示,之所以简介,是因为让人一目了然,不需要思考是什么时候. 在详细一点就是我们在微信朋友圈中 所看到的时间格式. 例如:刚刚 -几分钟前-几小时前等等. 今 ...

  8. Starling中通过PivotX 和 PivotY 修改原点

    一个显示对象的默认原点在左上角.addChild 是将它的左上角放在了父容器的(0, 0)位置. 如果将该显示对象的PivotX 和 PivotY 修改为其宽高的一半,那么它的原点就变到了该对象的中心 ...

  9. 2014年年度工作总结--IT狂人实录

    2014年也是我人生最重要的一年,她见证了我的成长与蜕变,让我从一个迷茫的旅者踏上一条柳暗花明的路. 春宇之行 从春宇短暂的9个月,却经历常人难以想想的风风雨雨,首先要感谢春宇公司给我带来了安逸宽松的 ...

  10. Solr部分更新MultiValued的Date日期字段时报错及解决方案

    问题描述如标题. 异常信息如下: Result Caused by: org.apache.solr.common.SolrException: Invalid Date String:'Mon Se ...