写在前面

  AutoMapper目录:

  本篇目录:

  上一篇《【道德经】漫谈实体、对象、DTO及AutoMapper的使用 》,因为内容写的有点跑偏,关于AutoMapper的使用最后只是简单写了下,很明显这种简单的使用方式不能满足项目中复杂的需要,网上找了下AutoMapper相关文档,但差不多都是像我一样简单的概述下,看来懒的不只有我一个,哈哈。在AutoMapper 官方文档中找到其使用的详细说明,天书一样的英文,然后就找相关中文文档,最后还是没找到,这边没办法,只能自己动手,丰衣足食了。英语牛逼的可以直接略过,查看英文文档,本篇也不算是翻译,因为本人英语实在拿不出手,只是按照示例加上自己的一些理解,做个学习笔记,不对的地方还请指正。

  注:虽然上一篇写跑偏了,但是本人真的很喜欢道德经,除了为人处世,在软件设计这方面其实也有体现,也希望可以运用到这上面,如果你和我有一样的想法,请在点击公告栏中的QQ链接,知音难觅啊!

Flattening-复杂到简单

  Flattening 翻译为压扁、拉平、扁平化的意思,可以理解为使原有复杂的结构变得简化,我们先看下领域模型和DTO代码:

  1. public class Order
  2. {
  3. private readonly IList<OrderLineItem> _orderLineItems = new List<OrderLineItem>();
  4. public Customer Customer { get; set; }
  5. public OrderLineItem[] GetOrderLineItems()
  6. {
  7. return _orderLineItems.ToArray();
  8. }
  9. public void AddOrderLineItem(Product product, int quantity)
  10. {
  11. _orderLineItems.Add(new OrderLineItem(product, quantity));
  12. }
  13. public decimal GetTotal()
  14. {
  15. return _orderLineItems.Sum(li => li.GetTotal());
  16. }
  17. }
  18.  
  19. public class Product
  20. {
  21. public decimal Price { get; set; }
  22. public string Name { get; set; }
  23. }
  24.  
  25. public class OrderLineItem
  26. {
  27. public OrderLineItem(Product product, int quantity)
  28. {
  29. Product = product;
  30. Quantity = quantity;
  31. }
  32. public Product Product { get; private set; }
  33. public int Quantity { get; private set; }
  34. public decimal GetTotal()
  35. {
  36. return Quantity * Product.Price;
  37. }
  38. }
  39.  
  40. public class Customer
  41. {
  42. public string Name { get; set; }
  43. }
  44.  
  45. public class OrderDto
  46. {
  47. public string CustomerName { get; set; }
  48. public decimal Total { get; set; }
  49. }

  可以看到领域模型 Order 是很复杂的,但是对于业务场景中的OrderDto却很简单,只有 CustomerName和Total两个属性,AutoMapper配置代码:

  1. public void Example()
  2. {
  3. var customer = new Customer
  4. {
  5. Name = "George Costanza"
  6. };
  7. var order = new Order
  8. {
  9. Customer = customer
  10. };
  11. var bosco = new Product
  12. {
  13. Name = "Bosco",
  14. Price = 4.99m
  15. };
  16. order.AddOrderLineItem(bosco, );
  17. // 配置 AutoMapper
  18. Mapper.CreateMap<Order, OrderDto>();
  19. // 执行 mapping
  20. OrderDto dto = Mapper.Map<Order, OrderDto>(order);
  21. Console.WriteLine("CustomerName:" + dto.CustomerName);
  22. Console.WriteLine("Total:" + dto.Total);
  23. }

  转换效果:

  可以看到配置相当的简单,只要设置下Order和OrderDto之间的类型映射就可以了,我们看OrderDto中的CustomerName和Total属性在领域模型Order中并没有与之相对性,没什么可以转换呢,感觉好神奇的样子,其实仔细发现这些属性的命名都有一定的规则,AutoMapper在做解析的时候会按照PascalCase(帕斯卡命名法),就是一种变量命名法,除了PascalCase还有Hungarian(匈牙利命名法)和camelCase(骆驼命名法),PascalCase就是指混合使用大小写字母来构成变量和函数的名字,首字母要大写,camelCase首字母小写,我们C#命名中,一般使用的是camelCase和PascalCase,比较高级的是PascalCase。

  但是为什么AutoMapper会解析Total呢?因为在领域模型Order中有个GetTotal()方法,AutoMapper会解析“Get”之后的单词,所以会与Total相对应,如果你把OrderDto的属性“Total”改为“Totals”,就会发现得到的“Totals”为0。理解了AutoMapper的解析方式,我们就要注意在编写变量、属性或是方法名称的时候一定要规范,这也是一种好的习惯。

Projection-简单到复杂

  Projection 翻译为投影,Flattening是由复杂结构简化,Projection正好相反,投影可以理解为由原始结构千变万化,我们看下两种转换结构:

  1. public class CalendarEvent
  2. {
  3. public DateTime EventDate { get; set; }
  4. public string Title { get; set; }
  5. }
  6.  
  7. public class CalendarEventForm
  8. {
  9. public DateTime EventDate { get; set; }
  10. public int EventHour { get; set; }
  11. public int EventMinute { get; set; }
  12. public string Title { get; set; }
  13. }

  CalendarEvent是原始结构,CalendarEventForm是我们需要转换后的结构,可以看到CalendarEventForm要比CalendarEvent结构复杂些,看下AutoMapper配置转换代码:

  1. public void Example()
  2. {
  3. var calendarEvent = new CalendarEvent
  4. {
  5. EventDate = new DateTime(, , , , , ),
  6. Title = "Company Holiday Party"
  7. };
  8.  
  9. // 配置 AutoMapper
  10. Mapper.CreateMap<CalendarEvent, CalendarEventForm>()
  11. .ForMember(dest => dest.EventDate, opt => opt.MapFrom(src => src.EventDate.Date))//定义映射规则
  12. .ForMember(dest => dest.EventHour, opt => opt.MapFrom(src => src.EventDate.Hour))//定义映射规则
  13. .ForMember(dest => dest.EventMinute, opt => opt.MapFrom(src => src.EventDate.Minute));//定义映射规则
  14.  
  15. // 执行 mapping
  16. CalendarEventForm form = Mapper.Map<CalendarEvent, CalendarEventForm>(calendarEvent);
  17.  
  18. Console.WriteLine("EventDate:"+form.EventDate);
  19. Console.WriteLine("EventHour:" + form.EventHour);
  20. Console.WriteLine("EventMinute:" + form.EventMinute);
  21. Console.WriteLine("Title:" + form.Title);
  22. }

  和Flattening不同的是,我们除了定义类型映射,还要自定义映射规则,src.EventDate.Date指向dest.EventDate,src.EventDate.Minute指向dest.EventMinute,src.EventDate.Hour指向dest.EventHour,当然我们还可以在MapFrom方法中做一些复杂的映射关系操作,MapFrom接受一个lambda表达式作为参数,可以是任何的Func表达式。Projection适用于由简单到复杂的结构映射,一般体现在业务场景很复杂的情况下。

  【更正:Projection也不一定适用在由简单到复杂的场景,应该说使用Projection就是把AutoMapper的映射配置交给用户来操作】

Configuration Validation-配置验证

  我们在使用Flattening的前提是我们需要转换的结构命名是没有错误的,但是如果我们没有使用PascalCase命名法,或者说我们命名是错误的,该怎么办呢?比如下面代码:

  1. public class Source
  2. {
  3. public int SomeValue { get; set; }
  4. }
  5.  
  6. public class Destination
  7. {
  8. public int SomeValuefff { get; set; }
  9. }

  可以看到Source和Destination中的字段并不相对应,我们测试下AutoMapper映射:

  AssertConfigurationIsValid方法是验证结构映射的,如果配置不正确,会报“AutoMapperConfigurationException”异常错误,如何解决这个问题?你可能会说,就不能改下SomeValuefff的名称吗?这种方法可以,但是如果业务场景中必须要使用怎么办呢,看了上面Projection的映射配置,你可能想到解决方法了,如下:

  1. Mapper.CreateMap<Source, Destination>()
  2. .ForMember(dest => dest.SomeValuefff, opt => opt.MapFrom(src => src.SomeValue));

  名称不对,我们可以自定义映射规则,虽然这种方式可以,但是如果业务场景中SomeValuefff并不需要,那我们改怎么办?既然有问题,就有解决之道,AutoMapper提供了Ignore方法,忽略不需要映射的数据结构,我们这样配置就可以了:

  1. Mapper.CreateMap<Source, Destination>()
  2. .ForMember(dest => dest.SomeValuefff, opt => opt.Ignore());

Lists and Array-集合和数组

  有时候我们除了类型映射之外,还需要对集合类型进行映射,先看个示例:

  1. public void Example()
  2. {
  3. var sources = new[]
  4. {
  5. new Source {Value = },
  6. new Source {Value = },
  7. new Source {Value = }
  8. };
  9. //配置AutoMapper
  10. Mapper.Initialize(cfg =>
  11. {
  12. cfg.CreateMap<Source, Destination>();
  13. });
  14. //配置和执行映射
  15. IEnumerable<Destination> ienumerableDest = Mapper.Map<Source[], IEnumerable<Destination>>(sources);
  16. ICollection<Destination> icollectionDest = Mapper.Map<Source[], ICollection<Destination>>(sources);
  17. IList<Destination> ilistDest = Mapper.Map<Source[], IList<Destination>>(sources);
  18. List<Destination> listDest = Mapper.Map<Source[], List<Destination>>(sources);
  19.  
  20. Console.WriteLine("ienumerableDest.Count:" + ienumerableDest.Count());
  21. Console.WriteLine("icollectionDest.Count:" + icollectionDest.Count());
  22. Console.WriteLine("ilistDest.Count:" + ilistDest.Count());
  23. Console.WriteLine("listDest.Count:" + listDest.Count());
  24. }

  转换结果:

  Source和Destination结构类型只有一个Value属性,可以看到对集合类型映射也很简单,只需要执行Mapper.Map泛型方法,指定需要转换的集合类型即可,AutoMapper所支持的集合类型包括:

  • IEnumerable
  • IEnumerable<T>
  • ICollection
  • ICollection<T>
  • IList
  • IList<T>
  • List<T>
  • Arrays

  我们在使用Mapper.Map执行类型映射的时候,如果来源类型支持上述集合类型,我们可以把来源类型省略掉,因为AutoMapper会自动判断传入对象sources的类型,如下:

  1. IEnumerable<Destination> ienumerableDest = Mapper.Map<IEnumerable<Destination>>(sources);
  2. ICollection<Destination> icollectionDest = Mapper.Map<ICollection<Destination>>(sources);
  3. IList<Destination> ilistDest = Mapper.Map<IList<Destination>>(sources);
  4. List<Destination> listDest = Mapper.Map<List<Destination>>(sources);

  还有一种情况是,在使用集合类型类型的时候,类型之间存在继承关系,例如下面我们需要转换的类型:

  1. public class ParentSource
  2. {
  3. public int Value1 { get; set; }
  4. }
  5. public class ChildSource : ParentSource
  6. {
  7. public int Value2 { get; set; }
  8. }
  9. public class ParentDestination
  10. {
  11. public int Value1 { get; set; }
  12. }
  13. public class ChildDestination : ParentDestination
  14. {
  15. public int Value2 { get; set; }
  16. }

  ChildSource继承ParentSource,ChildDestination继承ParentDestination,看下AutoMapper配置转换代码:

  1. public void Example()
  2. {
  3. var sources = new[]
  4. {
  5. new ParentSource(),
  6. new ChildSource(),
  7. new ParentSource()
  8. };
  9. //配置AutoMapper
  10. Mapper.Initialize(cfg =>
  11. {
  12. cfg.CreateMap<ParentSource, ParentDestination>()
  13. .Include<ChildSource, ChildDestination>();
  14. cfg.CreateMap<ChildSource, ChildDestination>();
  15. });
  16. //配置和执行映射
  17. var destinations = Mapper.Map<ParentSource[], ParentDestination[]>(sources);
  18. Console.WriteLine("destinations[0] Type:" + destinations[].GetType().ToString());
  19. Console.WriteLine("destinations[1] Type:" + destinations[].GetType().ToString());
  20. Console.WriteLine("destinations[2] Type:" + destinations[].GetType().ToString());
  21. }

  转换结果:

  注意在Initialize初始化CreateMap进行类型映射配置的时候有个Include泛型方法,签名为:“Include this configuration in derived types' maps”,大致意思为包含派生类型中配置,ChildSource是ParentSource的派生类,ChildDestination是ParentDestination的派生类,cfg.CreateMap<ParentSource, ParentDestination>().Include<ChildSource, ChildDestination>(); 这段代码只是说明ParentSource和ChildSource之间存在的关系,我们如果把这段代码注释掉,就会报上面“AutoMapperMappingException”类型指定不正确的异常错误,如果我们把下面这段代码:“cfg.CreateMap<ChildSource, ChildDestination>();”注释掉,转换结果为:

  虽然没有报“AutoMapperMappingException”异常,但是可以看出AutoMapper并没有从ChildSource类型映射到ChildDestination类型,而是自动映射到基类型,上面那段映射代码只是说明派生类和基类之间存在的关系,如果派生类需要映射的话,是需要添加派生类的映射的。

Nested mappings-嵌套映射

  我们上面说的集中映射方式都是简单类型映射,就是类型中并不包含其他类型的映射,如何在嵌套类型中执行映射?请看下面示例:

  1. public class OuterSource
  2. {
  3. public int Value { get; set; }
  4. public InnerSource Inner { get; set; }
  5. }
  6. public class InnerSource
  7. {
  8. public int OtherValue { get; set; }
  9. }
  10. public class OuterDest
  11. {
  12. public int Value { get; set; }
  13. public InnerDest Inner { get; set; }
  14. }
  15. public class InnerDest
  16. {
  17. public int OtherValue { get; set; }
  18. }

  OuterSource和OuterDest类型是我们需要映射的类型,可以看到OuterSource类型中嵌套了InnerSource类型,OuterDest类型中嵌套了InnerDest类型,AutoMapper类型映射配置代码:

  1. public void Example()
  2. {
  3. var source = new OuterSource
  4. {
  5. Value = ,
  6. Inner = new InnerSource { OtherValue = }
  7. };
  8. //配置AutoMapper
  9. Mapper.CreateMap<OuterSource, OuterDest>();
  10. Mapper.CreateMap<InnerSource, InnerDest>();
  11. //验证类型映射是否正确
  12. Mapper.AssertConfigurationIsValid();
  13. //执行映射
  14. var dest = Mapper.Map<OuterSource, OuterDest>(source);
  15. Console.WriteLine("dest.Value:" + dest.Value);
  16. Console.WriteLine("dest.Inner is null:" + (dest.Inner == null ? "true" : "false"));
  17. Console.WriteLine("dest.Inner.OtherValue:" + dest.Inner.OtherValue);
  18. }

  转换结果:

  上面代码中可以看出,对于嵌套映射,我们不需要配置什么,只要指定下类型映射关系和嵌套类型映射关系就可以了,也就是这段代码:“Mapper.CreateMap<InnerSource, InnerDest>();” 其实我们在验证类型映射的时候加上Mapper.AssertConfigurationIsValid(); 这段代码看是不是抛出“AutoMapperMappingException”异常来判断类型映射是否正确,因为AssertConfigurationIsValid方法没有返回值,只能在catch中捕获了,个人感觉AutoMapper可以提供个bool类型的返回值,验证成功则返回true。

后记

  示例代码下载:http://pan.baidu.com/s/10A7WM

  贪多嚼不烂,关于AutoMapper的使用先整理这些,后面会陆续更新,还请关注。

  AutoMapper在配置类型映射最注意的一点是,类型中的名称一定要按照PascalCase命名规则(Projection和Ignore除外)。

  如果你觉得本篇文章对你有所帮助,请点击右下部“推荐”,^_^

  参考资料:

【AutoMapper官方文档】DTO与Domin Model相互转换(上)的更多相关文章

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

    写在前面 AutoMapper目录: [AutoMapper官方文档]DTO与Domin Model相互转换(上) [AutoMapper官方文档]DTO与Domin Model相互转换(中) [Au ...

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

    写在前面 AutoMapper目录: [AutoMapper官方文档]DTO与Domin Model相互转换(上) [AutoMapper官方文档]DTO与Domin Model相互转换(中) [Au ...

  3. Spring Cloud官方文档中文版-Spring Cloud Config(上)

    官方文档地址为:http://cloud.spring.io/spring-cloud-static/Dalston.SR2/#spring-cloud-feign 文中例子我做了一些测试在:http ...

  4. Spring Cloud官方文档中文版-Spring Cloud Config(上)-服务端(配置中心)

    官方文档地址为:http://cloud.spring.io/spring-cloud-static/Dalston.SR2/#spring-cloud-feign 文中例子我做了一些测试在:http ...

  5. 喜大普奔!Django官方文档终于出中文版了

    喜大普奔!Django官方文档终于出中文版了 文章来源:企鹅号 - Crossin的编程教室 昨天经 Sur 同学告知才发现,Django 官方文档居然支持中文了! 之所以让我觉得惊喜与意外,是因为: ...

  6. Akka Typed 官方文档之随手记

    ️ 引言 近两年,一直在折腾用FP与OO共存的编程语言Scala,采取以函数式编程为主的方式,结合TDD和BDD的手段,采用Domain Driven Design的方法学,去构造DDDD应用(Dom ...

  7. Kotlin开发语言文档(官方文档)-- 目录

    开始阅读Kotlin官方文档.先上文档目录.有些内容还未阅读,有些目录标目翻译还需琢磨琢磨.后续再将具体内容的链接逐步加上. 文档链接:https://kotlinlang.org/docs/kotl ...

  8. Spring 4 官方文档学习(十二)View技术

    关键词:view technology.template.template engine.markup.内容较多,按需查用即可. 介绍 Thymeleaf Groovy Markup Template ...

  9. Spring 4 官方文档学习(十一)Web MVC 框架之HTTP caching support

    一个良好的HTTP缓存策略可以显著地增进web应用的性能和其客户端的体验.主要使用"Cache-Control" HTTP response header来完成,配合conditi ...

随机推荐

  1. Tomcat一个BUG造成CLOSE_WAIT

    之前应该提过,我们线上架构整体重新架设了,应用层面使用的是Spring Boot,前段日子因为一些第三方的原因,略有些匆忙的提前开始线上的内测了.然后运维发现了个问题,服务器的HTTPS端口有大量的C ...

  2. 菜鸟学Struts2——Interceptors

    昨天学习Struts2的Convention plugin,今天利用Convention plugin进行Interceptor学习,虽然是使用Convention plugin进行零配置开发,这只是 ...

  3. iOS热更新-8种实现方式

    一.JSPatch 热更新时,从服务器拉去js脚本.理论上可以修改和新建所有的模块,但是不建议这样做. 建议 用来做紧急的小需求和 修复严重的线上bug. 二.lua脚本 比如: wax.热更新时,从 ...

  4. CoreCRM 开发实录——Travis-CI 实现 .NET Core 程度在 macOS 上的构建和测试 [无水干货]

    上一篇文章我提到:为了使用"国货",我把 Linux 上的构建和测试委托给了 DaoCloud,而 Travis-CI 不能放着不用啊.还好,这货支持 macOS 系统.所以就把 ...

  5. ASP.NET MVC5+EF6+EasyUI 后台管理系统(81)-数据筛选(万能查询)

    系列目录 前言 听标题的名字似乎是一个非常牛X复杂的功能,但是实际上它确实是非常复杂的,我们本节将演示如何实现对数据,进行组合查询(数据筛选) 我们都知道Excel中是如何筛选数据的.就像下面一样 他 ...

  6. ASP.NET MVC5+EF6+EasyUI 后台管理系统(70)-微信公众平台开发-成为开发者

    系列目录 前言: 一.阅读这段系列之前,你必须花半天时间大致阅读微信公众平台的API文档,我尽量以简短快速的语言与大家分享一个过程 二.借助微信公众平台SDK Senparc.Weixin for C ...

  7. 2Sum

    用哈希表(unordered_map)使得时间复杂度从O(n*n)降到O(n),空间复杂度从O(1)增到O(n):一边找一边插入哈希表 注意 在C++11以前要使用unordered_map需要 #i ...

  8. centos 6.5 升级php

    1>追加CentOS 6.5的epel及remi源. # rpm -Uvh http://ftp.iij.ad.jp/pub/linux/fedora/epel/6/x86_64/epel-re ...

  9. Android快乐贪吃蛇游戏实战项目开发教程-06虚拟方向键(五)绘制方向键箭头

    本系列教程概述与目录:http://www.cnblogs.com/chengyujia/p/5787111.html本系列教程项目源码GitHub地址:https://github.com/jack ...

  10. 我想立刻辞职,然后闭关学习编程语言,我给自己3个月时间学习C语言!这样行的通吗

    文章背景,回答提问:我想立刻辞职,然后闭关学习编程语言,我给自己3个月时间学习C语言!这样行的通吗? 我的建议是这样:1. 不要辞职.首先说,你对整个开发没有一个简单的了解,或一个系统的入门学习.换句 ...