写在前面

  AutoMapper目录:

  本篇目录:

  上一篇《【道德经】漫谈实体、对象、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除外)。

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

  参考资料:

【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. .Net中的AOP系列之构建一个汽车租赁应用

    返回<.Net中的AOP>系列学习总目录 本篇目录 开始一个新项目 没有AOP的生活 变更的代价 使用AOP重构 本系列的源码本人已托管于Coding上:点击查看. 本系列的实验环境:VS ...

  2. Linux虚拟机的安装(使用Centos6.3)

    1.什么是虚拟机? 虚拟机指通过软件模拟的具有完整硬件系统功能的.运行在一个完全隔离环境中的完整计算机系统 2.安装Linux虚拟机前要做的准备 2.1:一台windows环境的pc 2.2:下载VM ...

  3. xpath提取多个标签下的text

    title: xpath提取多个标签下的text author: 青南 date: 2015-01-17 16:01:07 categories: [Python] tags: [xpath,Pyth ...

  4. 【原】AFNetworking源码阅读(二)

    [原]AFNetworking源码阅读(二) 本文转载请注明出处 —— polobymulberry-博客园 1. 前言 上一篇中我们在iOS Example代码中提到了AFHTTPSessionMa ...

  5. 移动端访问PC站点时自动跳转至移动站点

    方法一: 百度Site APP的uaredirect.js 实现手机访问,自动跳转 <script src="http://siteapp.baidu.com/static/webap ...

  6. 【C#附源码】数据库文档生成工具支持(Excel+Html)

    [2015] 很多时候,我们在生成数据库文档时,使用某些工具,可效果总不理想,不是内容不详细,就是表现效果一般般.很多还是word.html的.看着真是别扭.本人习惯用Excel,所以闲暇时,就简单的 ...

  7. Oracle 数据库知识汇总篇

    Oracle 数据库知识汇总篇(更新中..) 1.安装部署篇 2.管理维护篇 3.数据迁移篇 4.故障处理篇 5.性能调优篇 6.SQL PL/SQL篇 7.考试认证篇 8.原理体系篇 9.架构设计篇 ...

  8. [Nginx笔记]关于线上环境CLOSE_WAIT和TIME_WAIT过高

    运维的同学和Team里面的一个同学分别遇到过Nginx在线上环境使用中会遇到TIME_WAIT过高或者CLOSE_WAIT过高的状态 先从原因分析一下为什么,问题就迎刃而解了. 首先是TIME_WAI ...

  9. 解决“chrome提示adobe flash player 已经过期”的小问题

    这个小问题也确实困扰我许久,后来看到chrome吧里面有人给出了解决方案: 安装install_flash_player_ppapi, 该软件下载地址:http://labs.adobe.com/do ...

  10. 【干货分享】流程DEMO-制度发文和干部任免

    流程名: 制度发文和干部任免  业务描述: 当员工在该出勤的工作日出勤但漏打卡时,于一周内填写补打卡申请.  流程相关文件: 流程包.xml  流程说明: 直接导入流程包文件,即可使用本流程  表单: ...