DTO学习系列之AutoMapper(二)
本篇目录:
Flattening-复杂到简单
Projection-简单到复杂
Configuration Validation-配置验证
Lists and Array-集合和数组
Nested mappings-嵌套映射
后记
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, 15);
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(2008, 12, 15, 20, 30, 0),
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 = 5},
6 new Source {Value = 6},
7 new Source {Value = 7}
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[0].GetType().ToString());
19 Console.WriteLine("destinations[1] Type:" + destinations[1].GetType().ToString());
20 Console.WriteLine("destinations[2] Type:" + destinations[2].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 = 5,
6 Inner = new InnerSource { OtherValue = 15 }
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除外)。
如果你觉得本篇文章对你有所帮助,请点击右下部“推荐”,^_^
参考资料:
- 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
- http://www.cnblogs.com/xishuai/p/3700052.html
DTO学习系列之AutoMapper(二)的更多相关文章
- DTO学习系列之AutoMapper(四)
本篇目录: Mapping Inheritance-映射继承 Queryable Extensions (LINQ)-扩展查询表达式 Configuration-配置 Conditional Mapp ...
- DTO学习系列之AutoMapper(三)
本篇目录: Custom Type Converters-自定义类型转换器 Custom Value Resolvers-自定义值解析器 Null Substitution-空值替换 Containe ...
- DTO学习系列之AutoMapper(一)
一.前言 DTO(Data Transfer Object)数据传输对象,注意关键字“数据”两个字,并不是对象传输对象(Object Transfer Object),所以只是传输数据,并不包含领域业 ...
- DTO学习系列之AutoMapper(六)----EntityFramework和AutoMapper的婚后生活
写在前面 我到底是什么? 越界的可怕 做好自己 后记 文章标题主要关键字:mapping DTOs to Entities,注意并不是“Entities to DTOs”,表示实体对象到DTO的转换, ...
- DTO学习系列之AutoMapper(五)----当EntityFramework爱上AutoMapper
有时候相识即是一种缘分,相爱也不需要太多的理由,一个眼神足矣,当EntityFramework遇上AutoMapper,就是如此,恋爱虽易,相处不易. 在DDD(领域驱动设计)中,使用AutoMapp ...
- Dubbo学习系列之十二(Quartz任务调度)
Quartz词义为"石英"水晶,然后聪明的人类利用它发明了石英手表,因石英晶体在受到电流影响时,它会产生规律的振动,于是,这种时间上的规律,也被应用到了软件界,来命名了一款任务调度 ...
- WP8.1学习系列(第二十二章)——在页面之间导航
在本文中 先决条件 创建导航应用 Frame 和 Page 类 页面模板中的导航支持 在页面之间传递信息 缓存页面 摘要 后续步骤 相关主题 重要的 API Page Frame Navigation ...
- Python学习系列之(二)图解Windows8.1下安装Django
一. 下载 去官网下载https://www.djangoproject.com/download/最新版,最新版本是1.6 二. 安装: 将下载下来的Django-1.6.tar.gz解压到D盘,接 ...
- WP8.1学习系列(第十二章)——全景控件Panorama开发指南
2014/6/18 适用于:Windows Phone 8 和 Windows Phone Silverlight 8.1 | Windows Phone OS 7.1 全景体验是本机 Windows ...
随机推荐
- 修改Servlet模板
1.找到jar文件 查看MyEclipse根目录下的myeclipse.ini,找到Common文件夹的位置,打开文件夹..\Common\plugins 找到文件 com.genuitec.ecli ...
- java三层模型与三层架构
http://zhidao.baidu.com/question/316273675.html?qbl=relate_question_4 持久层用来固化数据,如常说的DAO层,操作数据库将数据入库业 ...
- php 输出昨天,今天,明天是星期几的方法
<?php //php判断某一天是星期几的方法 function getWeek($unixTime=''){ $unixTime=is_numeric($unixTime)?$unixTime ...
- Android再学习-20141111-Android应用的七大件
Android应用的七大件 应用程序的四大组件: Android的四大组件,使用时需要在程序中注册. Activity: Activity是应用程序的一个界面,可以通过这个界面查看联系人.打电话或者玩 ...
- python之7-1类
面向对象的编程,其实是将对象抽象成类,然后在类中,通过init定义实例初始化函数和多个操作实例的函数. 整个类就如同一个模板,我们可以用这个模板生成众多具现实例,并赋予实例动作. py中定义类的大致格 ...
- Touch事件
http://www.cnblogs.com/shawn-xie/archive/2012/12/07/2805582.html 前言 一个触屏网站到底和传统的pc端网站有什么区别呢,交互方式的改变首 ...
- 超实用--删除MYSQL中指定的数据的全部表
作过的人都知道,重复测试数据库的苦恼. 用法:# Usage: ./script user password dbnane mysql.nixcraft.in ~~~~~~~~~~~~~ #!/bin ...
- HDU 4116 Fruit Ninja
http://acm.hdu.edu.cn/showproblem.php?pid=4116 题意:给N个圆,求一条直线最多能经过几个圆?(相切也算) 思路:枚举中心圆,将其他圆的切线按照极角排序,并 ...
- Qt构建工具QBS之零 —— QBS 概览
本系列文章起因 自己非常喜欢 QT 这个框架, 使用 QT 这几年, IDE 一直是使用的 QT 自带的 Qt Creator, 这个 IDE 本身比较轻巧, 同事相关的语法提示之类的也算够用, 但是 ...
- IntelliJ IDEA: maven & jetty 开发 java web
之前使用eclipse + maven + jetty开发java web应用,本着no zuo no gain的想法, 折腾了一下Intellj idea下开发环境的搭建,顺带学习了maven re ...