【AutoMapper官方文档】DTO与Domin Model相互转换(上)
写在前面
AutoMapper目录:
- 【AutoMapper官方文档】DTO与Domin Model相互转换(上)
- 【AutoMapper官方文档】DTO与Domin Model相互转换(中)
- 【AutoMapper官方文档】DTO与Domin Model相互转换(下)
- 未完待续。。。
本篇目录:
上一篇《【道德经】漫谈实体、对象、DTO及AutoMapper的使用 》,因为内容写的有点跑偏,关于AutoMapper的使用最后只是简单写了下,很明显这种简单的使用方式不能满足项目中复杂的需要,网上找了下AutoMapper相关文档,但差不多都是像我一样简单的概述下,看来懒的不只有我一个,哈哈。在AutoMapper 官方文档中找到其使用的详细说明,天书一样的英文,然后就找相关中文文档,最后还是没找到,这边没办法,只能自己动手,丰衣足食了。英语牛逼的可以直接略过,查看英文文档,本篇也不算是翻译,因为本人英语实在拿不出手,只是按照示例加上自己的一些理解,做个学习笔记,不对的地方还请指正。
注:虽然上一篇写跑偏了,但是本人真的很喜欢道德经,除了为人处世,在软件设计这方面其实也有体现,也希望可以运用到这上面,如果你和我有一样的想法,请在点击公告栏中的QQ链接,知音难觅啊!
Flattening-复杂到简单
Flattening 翻译为压扁、拉平、扁平化的意思,可以理解为使原有复杂的结构变得简化,我们先看下领域模型和DTO代码:
- public class Order
- {
- private readonly IList<OrderLineItem> _orderLineItems = new List<OrderLineItem>();
- public Customer Customer { get; set; }
- public OrderLineItem[] GetOrderLineItems()
- {
- return _orderLineItems.ToArray();
- }
- public void AddOrderLineItem(Product product, int quantity)
- {
- _orderLineItems.Add(new OrderLineItem(product, quantity));
- }
- public decimal GetTotal()
- {
- return _orderLineItems.Sum(li => li.GetTotal());
- }
- }
- public class Product
- {
- public decimal Price { get; set; }
- public string Name { get; set; }
- }
- public class OrderLineItem
- {
- public OrderLineItem(Product product, int quantity)
- {
- Product = product;
- Quantity = quantity;
- }
- public Product Product { get; private set; }
- public int Quantity { get; private set; }
- public decimal GetTotal()
- {
- return Quantity * Product.Price;
- }
- }
- public class Customer
- {
- public string Name { get; set; }
- }
- public class OrderDto
- {
- public string CustomerName { get; set; }
- public decimal Total { get; set; }
- }
可以看到领域模型 Order 是很复杂的,但是对于业务场景中的OrderDto却很简单,只有 CustomerName和Total两个属性,AutoMapper配置代码:
- public void Example()
- {
- var customer = new Customer
- {
- Name = "George Costanza"
- };
- var order = new Order
- {
- Customer = customer
- };
- var bosco = new Product
- {
- Name = "Bosco",
- Price = 4.99m
- };
- order.AddOrderLineItem(bosco, );
- // 配置 AutoMapper
- Mapper.CreateMap<Order, OrderDto>();
- // 执行 mapping
- OrderDto dto = Mapper.Map<Order, OrderDto>(order);
- Console.WriteLine("CustomerName:" + dto.CustomerName);
- Console.WriteLine("Total:" + dto.Total);
- }
转换效果:
可以看到配置相当的简单,只要设置下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正好相反,投影可以理解为由原始结构千变万化,我们看下两种转换结构:
- public class CalendarEvent
- {
- public DateTime EventDate { get; set; }
- public string Title { get; set; }
- }
- public class CalendarEventForm
- {
- public DateTime EventDate { get; set; }
- public int EventHour { get; set; }
- public int EventMinute { get; set; }
- public string Title { get; set; }
- }
CalendarEvent是原始结构,CalendarEventForm是我们需要转换后的结构,可以看到CalendarEventForm要比CalendarEvent结构复杂些,看下AutoMapper配置转换代码:
- public void Example()
- {
- var calendarEvent = new CalendarEvent
- {
- EventDate = new DateTime(, , , , , ),
- Title = "Company Holiday Party"
- };
- // 配置 AutoMapper
- Mapper.CreateMap<CalendarEvent, CalendarEventForm>()
- .ForMember(dest => dest.EventDate, opt => opt.MapFrom(src => src.EventDate.Date))//定义映射规则
- .ForMember(dest => dest.EventHour, opt => opt.MapFrom(src => src.EventDate.Hour))//定义映射规则
- .ForMember(dest => dest.EventMinute, opt => opt.MapFrom(src => src.EventDate.Minute));//定义映射规则
- // 执行 mapping
- CalendarEventForm form = Mapper.Map<CalendarEvent, CalendarEventForm>(calendarEvent);
- Console.WriteLine("EventDate:"+form.EventDate);
- Console.WriteLine("EventHour:" + form.EventHour);
- Console.WriteLine("EventMinute:" + form.EventMinute);
- Console.WriteLine("Title:" + form.Title);
- }
和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命名法,或者说我们命名是错误的,该怎么办呢?比如下面代码:
- public class Source
- {
- public int SomeValue { get; set; }
- }
- public class Destination
- {
- public int SomeValuefff { get; set; }
- }
可以看到Source和Destination中的字段并不相对应,我们测试下AutoMapper映射:
AssertConfigurationIsValid方法是验证结构映射的,如果配置不正确,会报“AutoMapperConfigurationException”异常错误,如何解决这个问题?你可能会说,就不能改下SomeValuefff的名称吗?这种方法可以,但是如果业务场景中必须要使用怎么办呢,看了上面Projection的映射配置,你可能想到解决方法了,如下:
- Mapper.CreateMap<Source, Destination>()
- .ForMember(dest => dest.SomeValuefff, opt => opt.MapFrom(src => src.SomeValue));
名称不对,我们可以自定义映射规则,虽然这种方式可以,但是如果业务场景中SomeValuefff并不需要,那我们改怎么办?既然有问题,就有解决之道,AutoMapper提供了Ignore方法,忽略不需要映射的数据结构,我们这样配置就可以了:
- Mapper.CreateMap<Source, Destination>()
- .ForMember(dest => dest.SomeValuefff, opt => opt.Ignore());
Lists and Array-集合和数组
有时候我们除了类型映射之外,还需要对集合类型进行映射,先看个示例:
- public void Example()
- {
- var sources = new[]
- {
- new Source {Value = },
- new Source {Value = },
- new Source {Value = }
- };
- //配置AutoMapper
- Mapper.Initialize(cfg =>
- {
- cfg.CreateMap<Source, Destination>();
- });
- //配置和执行映射
- IEnumerable<Destination> ienumerableDest = Mapper.Map<Source[], IEnumerable<Destination>>(sources);
- ICollection<Destination> icollectionDest = Mapper.Map<Source[], ICollection<Destination>>(sources);
- IList<Destination> ilistDest = Mapper.Map<Source[], IList<Destination>>(sources);
- List<Destination> listDest = Mapper.Map<Source[], List<Destination>>(sources);
- Console.WriteLine("ienumerableDest.Count:" + ienumerableDest.Count());
- Console.WriteLine("icollectionDest.Count:" + icollectionDest.Count());
- Console.WriteLine("ilistDest.Count:" + ilistDest.Count());
- Console.WriteLine("listDest.Count:" + listDest.Count());
- }
转换结果:
Source和Destination结构类型只有一个Value属性,可以看到对集合类型映射也很简单,只需要执行Mapper.Map泛型方法,指定需要转换的集合类型即可,AutoMapper所支持的集合类型包括:
- IEnumerable
- IEnumerable<T>
- ICollection
- ICollection<T>
- IList
- IList<T>
- List<T>
- Arrays
我们在使用Mapper.Map执行类型映射的时候,如果来源类型支持上述集合类型,我们可以把来源类型省略掉,因为AutoMapper会自动判断传入对象sources的类型,如下:
- IEnumerable<Destination> ienumerableDest = Mapper.Map<IEnumerable<Destination>>(sources);
- ICollection<Destination> icollectionDest = Mapper.Map<ICollection<Destination>>(sources);
- IList<Destination> ilistDest = Mapper.Map<IList<Destination>>(sources);
- List<Destination> listDest = Mapper.Map<List<Destination>>(sources);
还有一种情况是,在使用集合类型类型的时候,类型之间存在继承关系,例如下面我们需要转换的类型:
- public class ParentSource
- {
- public int Value1 { get; set; }
- }
- public class ChildSource : ParentSource
- {
- public int Value2 { get; set; }
- }
- public class ParentDestination
- {
- public int Value1 { get; set; }
- }
- public class ChildDestination : ParentDestination
- {
- public int Value2 { get; set; }
- }
ChildSource继承ParentSource,ChildDestination继承ParentDestination,看下AutoMapper配置转换代码:
- public void Example()
- {
- var sources = new[]
- {
- new ParentSource(),
- new ChildSource(),
- new ParentSource()
- };
- //配置AutoMapper
- Mapper.Initialize(cfg =>
- {
- cfg.CreateMap<ParentSource, ParentDestination>()
- .Include<ChildSource, ChildDestination>();
- cfg.CreateMap<ChildSource, ChildDestination>();
- });
- //配置和执行映射
- var destinations = Mapper.Map<ParentSource[], ParentDestination[]>(sources);
- Console.WriteLine("destinations[0] Type:" + destinations[].GetType().ToString());
- Console.WriteLine("destinations[1] Type:" + destinations[].GetType().ToString());
- Console.WriteLine("destinations[2] Type:" + destinations[].GetType().ToString());
- }
转换结果:
注意在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-嵌套映射
我们上面说的集中映射方式都是简单类型映射,就是类型中并不包含其他类型的映射,如何在嵌套类型中执行映射?请看下面示例:
- public class OuterSource
- {
- public int Value { get; set; }
- public InnerSource Inner { get; set; }
- }
- public class InnerSource
- {
- public int OtherValue { get; set; }
- }
- public class OuterDest
- {
- public int Value { get; set; }
- public InnerDest Inner { get; set; }
- }
- public class InnerDest
- {
- public int OtherValue { get; set; }
- }
OuterSource和OuterDest类型是我们需要映射的类型,可以看到OuterSource类型中嵌套了InnerSource类型,OuterDest类型中嵌套了InnerDest类型,AutoMapper类型映射配置代码:
- public void Example()
- {
- var source = new OuterSource
- {
- Value = ,
- Inner = new InnerSource { OtherValue = }
- };
- //配置AutoMapper
- Mapper.CreateMap<OuterSource, OuterDest>();
- Mapper.CreateMap<InnerSource, InnerDest>();
- //验证类型映射是否正确
- Mapper.AssertConfigurationIsValid();
- //执行映射
- var dest = Mapper.Map<OuterSource, OuterDest>(source);
- Console.WriteLine("dest.Value:" + dest.Value);
- Console.WriteLine("dest.Inner is null:" + (dest.Inner == null ? "true" : "false"));
- Console.WriteLine("dest.Inner.OtherValue:" + dest.Inner.OtherValue);
- }
转换结果:
上面代码中可以看出,对于嵌套映射,我们不需要配置什么,只要指定下类型映射关系和嵌套类型映射关系就可以了,也就是这段代码:“Mapper.CreateMap<InnerSource, InnerDest>();” 其实我们在验证类型映射的时候加上Mapper.AssertConfigurationIsValid(); 这段代码看是不是抛出“AutoMapperMappingException”异常来判断类型映射是否正确,因为AssertConfigurationIsValid方法没有返回值,只能在catch中捕获了,个人感觉AutoMapper可以提供个bool类型的返回值,验证成功则返回true。
后记
示例代码下载:http://pan.baidu.com/s/10A7WM
贪多嚼不烂,关于AutoMapper的使用先整理这些,后面会陆续更新,还请关注。
AutoMapper在配置类型映射最注意的一点是,类型中的名称一定要按照PascalCase命名规则(Projection和Ignore除外)。
如果你觉得本篇文章对你有所帮助,请点击右下部“推荐”,^_^
参考资料:
- https://github.com/AutoMapper/AutoMapper/wiki
- http://www.cnblogs.com/dudu/archive/2011/12/16/2284828.html
- http://www.cnblogs.com/ego/archive/2009/05/13/1456363.html
- http://www.cnblogs.com/jiguixin/archive/2011/09/19/2181521.html
- http://blog.csdn.net/yujunwu2525/article/details/7850486
【AutoMapper官方文档】DTO与Domin Model相互转换(上)的更多相关文章
- 【AutoMapper官方文档】DTO与Domin Model相互转换(中)
写在前面 AutoMapper目录: [AutoMapper官方文档]DTO与Domin Model相互转换(上) [AutoMapper官方文档]DTO与Domin Model相互转换(中) [Au ...
- 【AutoMapper官方文档】DTO与Domin Model相互转换(下)
写在前面 AutoMapper目录: [AutoMapper官方文档]DTO与Domin Model相互转换(上) [AutoMapper官方文档]DTO与Domin Model相互转换(中) [Au ...
- Spring Cloud官方文档中文版-Spring Cloud Config(上)
官方文档地址为:http://cloud.spring.io/spring-cloud-static/Dalston.SR2/#spring-cloud-feign 文中例子我做了一些测试在:http ...
- Spring Cloud官方文档中文版-Spring Cloud Config(上)-服务端(配置中心)
官方文档地址为:http://cloud.spring.io/spring-cloud-static/Dalston.SR2/#spring-cloud-feign 文中例子我做了一些测试在:http ...
- 喜大普奔!Django官方文档终于出中文版了
喜大普奔!Django官方文档终于出中文版了 文章来源:企鹅号 - Crossin的编程教室 昨天经 Sur 同学告知才发现,Django 官方文档居然支持中文了! 之所以让我觉得惊喜与意外,是因为: ...
- Akka Typed 官方文档之随手记
️ 引言 近两年,一直在折腾用FP与OO共存的编程语言Scala,采取以函数式编程为主的方式,结合TDD和BDD的手段,采用Domain Driven Design的方法学,去构造DDDD应用(Dom ...
- Kotlin开发语言文档(官方文档)-- 目录
开始阅读Kotlin官方文档.先上文档目录.有些内容还未阅读,有些目录标目翻译还需琢磨琢磨.后续再将具体内容的链接逐步加上. 文档链接:https://kotlinlang.org/docs/kotl ...
- Spring 4 官方文档学习(十二)View技术
关键词:view technology.template.template engine.markup.内容较多,按需查用即可. 介绍 Thymeleaf Groovy Markup Template ...
- Spring 4 官方文档学习(十一)Web MVC 框架之HTTP caching support
一个良好的HTTP缓存策略可以显著地增进web应用的性能和其客户端的体验.主要使用"Cache-Control" HTTP response header来完成,配合conditi ...
随机推荐
- Tomcat一个BUG造成CLOSE_WAIT
之前应该提过,我们线上架构整体重新架设了,应用层面使用的是Spring Boot,前段日子因为一些第三方的原因,略有些匆忙的提前开始线上的内测了.然后运维发现了个问题,服务器的HTTPS端口有大量的C ...
- 菜鸟学Struts2——Interceptors
昨天学习Struts2的Convention plugin,今天利用Convention plugin进行Interceptor学习,虽然是使用Convention plugin进行零配置开发,这只是 ...
- iOS热更新-8种实现方式
一.JSPatch 热更新时,从服务器拉去js脚本.理论上可以修改和新建所有的模块,但是不建议这样做. 建议 用来做紧急的小需求和 修复严重的线上bug. 二.lua脚本 比如: wax.热更新时,从 ...
- CoreCRM 开发实录——Travis-CI 实现 .NET Core 程度在 macOS 上的构建和测试 [无水干货]
上一篇文章我提到:为了使用"国货",我把 Linux 上的构建和测试委托给了 DaoCloud,而 Travis-CI 不能放着不用啊.还好,这货支持 macOS 系统.所以就把 ...
- ASP.NET MVC5+EF6+EasyUI 后台管理系统(81)-数据筛选(万能查询)
系列目录 前言 听标题的名字似乎是一个非常牛X复杂的功能,但是实际上它确实是非常复杂的,我们本节将演示如何实现对数据,进行组合查询(数据筛选) 我们都知道Excel中是如何筛选数据的.就像下面一样 他 ...
- ASP.NET MVC5+EF6+EasyUI 后台管理系统(70)-微信公众平台开发-成为开发者
系列目录 前言: 一.阅读这段系列之前,你必须花半天时间大致阅读微信公众平台的API文档,我尽量以简短快速的语言与大家分享一个过程 二.借助微信公众平台SDK Senparc.Weixin for C ...
- 2Sum
用哈希表(unordered_map)使得时间复杂度从O(n*n)降到O(n),空间复杂度从O(1)增到O(n):一边找一边插入哈希表 注意 在C++11以前要使用unordered_map需要 #i ...
- 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 ...
- Android快乐贪吃蛇游戏实战项目开发教程-06虚拟方向键(五)绘制方向键箭头
本系列教程概述与目录:http://www.cnblogs.com/chengyujia/p/5787111.html本系列教程项目源码GitHub地址:https://github.com/jack ...
- 我想立刻辞职,然后闭关学习编程语言,我给自己3个月时间学习C语言!这样行的通吗
文章背景,回答提问:我想立刻辞职,然后闭关学习编程语言,我给自己3个月时间学习C语言!这样行的通吗? 我的建议是这样:1. 不要辞职.首先说,你对整个开发没有一个简单的了解,或一个系统的入门学习.换句 ...