一文为你详细讲解对象映射库【AutoMapper】所支持场景
前言
在AutoMapper未出世前,对象与对象之间的映射,我们只能通过手动为每个属性一一赋值,时间长了不仅是我们而且老外也觉得映射代码很无聊啊。这个时候老外的所写的强大映射库AutoMapper横空出世,AutoMapper是一个对象映射库, 它提供简单的类型配置,以及简单的映射测试。对象映射通过将一种类型的输入对象转换为不同类型的输出对象而起作用。项目之前有用过,但是对其了解不够透彻映射时有时候会抛异常,后来弃之,本节我们来详细了解下AutoMapper映射库。
AutoMapper基础版
在AutoMapper中创建映射配置有两种方式。一种是通过实例化MapperConfiguration类来配置,一种是通过类Mapper中的静态方法Initialize来配置,下面我们来看看。
public class User
{
public int Id { get; set; }
public int Age { get; set; }
public string Name { get; set; }
} public class UserDTO
{
public int Id { get; set; }
public int Age { get; set; }
public string Name { get; set; }
}
static void Main(string[] args)
{
var user = new User()
{
Id = ,
Age = ,
Name = "Jeffcky"
}; var config = new MapperConfiguration(cfg => cfg.CreateMap<User, UserDTO>());
//或者Mapper.Initialize(cfg => cfg.CreateMap<User, UserDTO>()); var mapper = config.CreateMapper();
//或者var mapper = new Mapper(config); //最终调用Map方法进行映射
var userDTO = mapper.Map<User, UserDTO>(user);
Console.ReadKey();
}
在Map映射方法中有两个参数,我们通俗讲则是从一个映射到另一个对象,在AutoMapper中将其称为映射源和映射目标。
关于本节映射都通过如下静态方法来实现,简单粗暴。
Mapper.Initialize(cfg => cfg.CreateMap<User, UserDTO>()); var userDTO = Mapper.Map<User, UserDTO>(user);
接下来我们再来看若映射源为空,那么是否会进行映射,还是抛异常呢?
static void Main(string[] args)
{
User user = null; Mapper.Initialize(cfg => cfg.CreateMap<User, UserDTO>()); var userDTO = Mapper.Map<User, UserDTO>(user); Console.ReadKey();
}
到此我们总结出一点:AutoMapper将映射源映射到目标时,AutoMapper将忽略空引用异常。 这是AutoMapper默认设计。
是不是到此关于AutoMapper就讲完了呢?童鞋想想所有场景嘛,这个只是最简单的场景,或者天马行空想想其他问题看看AutoMapper支持不,比如我想想,AutoMapper对属性大小写是否敏感呢?想完就开干啊。我们将User对象属性全部改为小写:
public class User
{
public int id { get; set; }
public int age { get; set; }
public string name { get; set; }
}
static void Main(string[] args)
{
var user = new User()
{
id = ,
age = ,
name = "Jeffcky"
}; Mapper.Initialize(cfg => cfg.CreateMap<User, UserDTO>()); var userDTO = Mapper.Map<User, UserDTO>(user); Console.ReadKey();
}
到这里我们又可以总结出一点:AutoMapper从映射源到映射目标时不区分大小写。
AutoMapper中级版
我们讲完基础版,接下来来进入中级版看看AutoMapper到底有多强,磕不屎你哟。是否支持继承映射哎。
public class Base
{
public int Id { get; set; }
public DateTime CreatedTime { get; set; }
public DateTime ModifiedTime { get; set; }
}
public class User : Base
{
public int Age { get; set; }
public string Name { get; set; }
}
public class UserDTO
{
public int Id { get; set; }
public DateTime CreatedTime { get; set; }
public DateTime ModifiedTime { get; set; }
public int Age { get; set; }
public string Name { get; set; }
}
var user = new User()
{
Id = ,
Age = ,
Name = "Jeffcky",
CreatedTime = DateTime.Now,
ModifiedTime = DateTime.Now
}; Mapper.Initialize(cfg => cfg.CreateMap<User, UserDTO>()); var userDTO = Mapper.Map<User, UserDTO>(user);
好了,看来也是支持的,我们总结来一个:AutoMapper从映射源到映射目标支持继承。讲完关于类的继承,我们来看看复杂对象,这下AutoMapper想必要有点挑战了吧。
public class Address
{
public string City { get; set; }
public string State { get; set; }
public string Country { get; set; } }
public class AuthorModel
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public Address Address { get; set; }
}
public class AuthorDTO
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string City { get; set; }
public string State { get; set; }
public string Country { get; set; }
}
static void Main(string[] args)
{
var author = new AuthorModel()
{
Id = ,
FirstName = "Wang",
LastName = "Jeffcky",
Address = new Address()
{
City = "深圳",
State = "",
Country = "中国"
}
}; Mapper.Initialize(cfg => cfg.CreateMap<AuthorModel, AuthorDTO>()); var authorDTO = Mapper.Map<AuthorModel, AuthorDTO>(author); Console.ReadKey();
}
哇喔,我说AutoMapper还能有这么智能,那还要我们程序员干嘛,在AuthorDTO中我们将Address扁平化为简单属性,所以此时利用Map不再是万能的,我们需要手动在创建映射配置时通过ForMember方法来自定义指定映射属性来源,从映射源中的Address复杂对象属性到AuthorDTO中属性上。
var author = new AuthorModel()
{
Id = ,
FirstName = "Wang",
LastName = "Jeffcky",
Address = new Address()
{
City = "深圳",
State = "",
Country = "中国"
}
}; Mapper.Initialize(cfg => cfg.CreateMap<AuthorModel, AuthorDTO>()
.ForMember(d => d.City, o => o.MapFrom(s => s.Address.City))
.ForMember(d => d.State, o => o.MapFrom(s => s.Address.State))
.ForMember(d => d.Country, o => o.MapFrom(s => s.Address.Country))
);
var authorDTO = Mapper.Map<AuthorModel, AuthorDTO>(author);
如上所给片段代码,对于AuthorDTO中的City属性,我们指定其值来源于映射源中复杂属性Address中的City,其余同理,同时对于其他在相同层次上的属性不会进行覆盖。
默认情况下AutoMapper会将同名且不区分大小写的属性进行映射,比如对于有些属性为了节省传输流量且完全不需要用到的属性,我们压根没必要进行映射,此时AutoMapper中有Ignore方法来忽略映射,如下代码片段将忽略对属性Id的映射。
Mapper.Initialize(cfg => cfg.CreateMap<AuthorModel, AuthorDTO>()
.ForMember(d => d.Id, o => o.Ignore())
);
到此我们又可以来一个总结:AutoMapper支持从映射源到映射目标的扁平化。实际上AutoMapper支持扁平化映射,但是前提是遵守AutoMapper映射约定才行,我们走一个。
public class Customer
{
public Company Company { get; set; } } public class Company
{
public string Name { get; set; }
} public class CustomerDTO
{
public string CompanyName { get; set; }
}
static void Main(string[] args)
{
var customer = new Customer()
{
Company = new Company()
{
Name = "腾讯"
}
}; Mapper.Initialize(cfg =>
{
cfg.CreateMap<Customer, CustomerDTO>();
}); var customerDTO = Mapper.Map<Customer, CustomerDTO>(customer); Console.ReadKey();
}
你看我们什么都没做,结果同样还是映射到了目标类中,不过是遵守了AutoMapper的映射约定罢了,看到这个想必大家就马上明白过来了。如果扁平化映射源类,若想AutoMapper依然能够自动映射,那么映射目标类中的属性必须是映射源中复杂属性名称加上复杂属性中的属性名称才行,因为AutoMapper会深度搜索目标类,直到找到匹配的属性为止。下面我们再来看看集合映射。
public class Customer
{
public int Id { get; set; }
public string Name { get; set; }
public IEnumerable<Order> Orders { get; set; }
} public class Order
{
public int Id { get; set; }
public string TradeNo { get; set; }
public int TotalFee { get; set; }
} public class CustomerDTO
{
public int Id { get; set; }
public string Name { get; set; }
public IEnumerable<OrderDTO> OrderDTOs { get; set; }
} public class OrderDTO
{
public int Id { get; set; }
public string TradeNo { get; set; }
public int TotalFee { get; set; }
}
上述Customer对象中有Order的集合属性,所以怕AutoMapper是映射不了,我们手动配置一下,如下:
static void Main(string[] args)
{
var customer = new Customer()
{
Id = ,
Name = "Jeffcky",
Orders = new List<Order>()
{
new Order()
{
Id =,
TotalFee = ,
TradeNo = ""
}
}
}; Mapper.Initialize(cfg => cfg.CreateMap<Customer, CustomerDTO>()
.ForMember(d => d.OrderDTOs, o => o.MapFrom(s => s.Orders))
);
var customerDTO = Mapper.Map<Customer, CustomerDTO>(customer); Console.ReadKey();
}
喔,抛出异常了,哈哈,果然AutoMapper还有不支持的,果断弃之(我们项目当时就是一直出这样的问题于是乎弃用了)。慢着,老铁。利用AutoMapper映射大部分情况下都会遇到如上异常,所以我们来分析下,在AutoMapper中,当它偶遇一个接口的目标对象时,它会自动生成动态代理类,怎么感觉好像说到EntityFramework了。 当映射到不存在的映射目标时,这就是内部设计的行为了。 然而然而,我们映射目标类却存在啊,于是乎我修改了AutoMapper映射,将Order到OrderDTO也进行映射配置,然后在配置映射Customer对象再指定Order集合属性,我们试试。
Mapper.Initialize(cfg =>
{
cfg.CreateMap<Order, OrderDTO>();
cfg.CreateMap<Customer, CustomerDTO>()
.ForMember(d => d.OrderDTOs, o => o.MapFrom(s => Mapper.Map<IList<Order>, IList<OrderDTO>>(s.Orders)));
});
var customerDTO = Mapper.Map<Customer, CustomerDTO>(customer);
老铁妥妥没毛病,通过此种方式即使嵌套多层依然也是能够解析,只不过我们得手动多几个配置罢了不是,这里我们又来一个结论:在映射复杂对象中的集合属性时,我们需要配置集合属性的映射,然后在复杂对象中再次映射集合属性。
2017-10-13补充
在写Demo项目时发现还有一种很常见的场景,但是若不注意也会映射出错,下面我们来看看。
public class User
{
public string UserName { get; set; }
public string Email { get; set; }
public string Password { get; set; }
public virtual UserProfile UserProfile { get; set; }
} public class UserProfile
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string Address { get; set; }
public virtual User User { get; set; }
}
public class UserDTO
{
public Int64 ID { get; set; }
[Display(Name ="First Name")]
public string FirstName { get; set; }
[Display(Name="Last Name")]
public string LastName { get; set; }
public string Address { get; set; }
[Display(Name="User Name")]
public string UserName { get; set; }
public string Email { get; set; }
public string Password { get; set; }
[Display(Name ="Added Date")]
public DateTime AddedDate { get; set; }
}
同样是扁平化,接下来我们再来进行映射
CreateMap<User, UserDTO>()
.ForMember(d => d.FirstName, m => m.MapFrom(f => f.UserProfile.FirstName))
.ForMember(d => d.LastName, m => m.MapFrom(f => f.UserProfile.LastName))
.ForMember(d => d.Address, m => m.MapFrom(f => f.UserProfile.Address)); CreateMap<UserDTO, User>()
.ForMember(d => d.UserProfile.FirstName, m => m.MapFrom(f => f.FirstName))
.ForMember(d => d.UserProfile.LastName, m => m.MapFrom(f => f.LastName))
.ForMember(d => d.UserProfile.Address, m => m.MapFrom(f => f.Address));
此时我们当然可以利用AfterMap来实现,但是还是有其他解决方案,如下:
CreateMap<UserDTO, User>()
.ForMember(d => d.UserProfile, m => m.MapFrom(f => f)); CreateMap<UserDTO, UserProfile>()
.ForMember(d => d.FirstName, m => m.MapFrom(f => f.FirstName))
.ForMember(d => d.LastName, m => m.MapFrom(f => f.LastName))
.ForMember(d => d.Address, m => m.MapFrom(f => f.Address));
AutoMapper高级版
AutoMapper太强大了,我给跪了,强大到这篇幅不够,得手动下拉滚动条继续磕。废话少说,我们再来看看AutoMapper使用高级版,自定义值解析,动态对象映射、类型转换等。
自定义值解析
AutoMapper支持自定义解析,只不过我们需要实现IValueResolver接口才行,下面我们来看看。
public class Customer
{
public bool VIP { get; set; }
} public class CustomerDTO
{
public string VIP { get; set; }
}
实现IValueResolver接口,对映射源加以判断返回映射目标中的字符串。
public class VIPResolver : IValueResolver<Customer, CustomerDTO, string>
{
public string Resolve(Customer source, CustomerDTO destination, string destMember, ResolutionContext context)
{
return source.VIP ? "Y" : "N";
}
}
然后在映射配置时使用ResolveUsing来实现上述自定义解析,使用方式有如下两种。
var customer = new Customer()
{
VIP = true
}; Mapper.Initialize(cfg =>
{
cfg.CreateMap<Customer, CustomerDTO>()
.ForMember(cv => cv.VIP, m => m.ResolveUsing<VIPResolver>());
}); //或者
//Mapper.Initialize(cfg =>
//{
// cfg.CreateMap<Customer, CustomerDTO>()
// .ForMember(cv => cv.VIP, m => m.ResolveUsing(new VIPResolver()));
//});
var customerDTO = Mapper.Map<Customer, CustomerDTO>(customer);
动态对象映射
public class Customer
{
public int Id { get; set; }
public string Name { get; set; }
}
dynamic customer = new ExpandoObject();
customer.Id = ;
customer.Name = "Jeffcky"; Mapper.Initialize(cfg => { }); var result = Mapper.Map<Customer>(customer); dynamic foo2 = Mapper.Map<ExpandoObject>(result);
类型转换
关于上述自定义值解析,我们同样可以用类型转换类实现,在AutoMapper中存在ConvertUsing方法,该方法类似于C#中的投影一样,如下:
Mapper.Initialize(cfg =>
{
cfg.CreateMap<Customer, CustomerDTO>()
.ConvertUsing(s => new CustomerDTO()
{
VIP = s.VIP ? "Y" : "N"
});
});
或者
public class CustomTypeConverter : ITypeConverter<Customer, CustomerDTO>
{
public CustomerDTO Convert(Customer source, CustomerDTO destination, ResolutionContext context)
{
return new CustomerDTO
{
VIP = source.VIP ? "Y" : "N",
};
}
}
Mapper.Initialize(cfg =>
{
cfg.CreateMap<Customer, CustomerDTO>()
.ConvertUsing(new CustomTypeConverter());
});
AutoMapper太强大了,上述已经给出大部分我们基本上会用到的场景,AutoMapper还支持依赖注入,同时最爽的是有了AutoMapper.QueryableExtensions扩展方法,这针对使用EF的童鞋简直是福音啊。 通过ProjectTo方法即可映射从数据库查询出的IQueryable类型数据。
IQueryable<Customer> customers = null; var customersDTO = customers.ProjectTo<CustomerDTO>();
总结
AutoMapper强大到给跪了,目前该项目已被.NET基金会所支持,看过的,路过的,没用过的,赶紧走起用起来啊,有时间还会更新AutoMapper其他用途,想必上述场景已经够我们用了吧,如果你觉得不够用,请私信我,我再加上啊。
一文为你详细讲解对象映射库【AutoMapper】所支持场景的更多相关文章
- .NET的对象映射工具AutoMapper使用笔记
AutoMapper是一个.NET的对象映射工具. 项目地址:https://github.com/AutoMapper/AutoMapper. 帮助文档:https://github.com/Aut ...
- 对象映射工具AutoMapper介绍
AutoMapper是用来解决对象之间映射转换的类库.对于我们开发人员来说,写对象之间互相转换的代码是一件极其浪费生命的事情,AutoMapper能够帮助我们节省不少时间. 一. AutoMapper ...
- 【5min+】 对象映射只有AutoMapper?试试Mapster
系列介绍 [五分钟的dotnet]是一个利用您的碎片化时间来学习和丰富.net知识的博文系列.它所包含了.net体系中可能会涉及到的方方面面,比如C#的小细节,AspnetCore,微服务中的.net ...
- ASP.NET Core 中的对象映射之 AutoMapper
目录 AutoMapper 简介 AutoMapper 使用 初始化 Profile设置 扁平化映射 集合映射 投影 条件映射 值转换 设置转换前后行为 配置验证及设置 反向映射 自定义转换器 自定义 ...
- [转]对象映射类AutoMapper的使用
由于原文太长了,此处就直接贴上原文的超链接,大家自行学习. codeproject中的一篇文章: AutoMapper
- C# 高性能对象映射(表达式树实现)
前言 上篇简单实现了对象映射,针对数组,集合,嵌套类并没有给出实现,这一篇继续完善细节. 开源对象映射类库映射分析 1.AutoMapper 实现原理:主要通过表达式树Api 实现对象映射 优点: . ...
- 从壹开始前后端分离【 .NET Core2.0 +Vue2.0 】框架之十三 || DTOs 对象映射使用,项目部署Windows+Linux完整版
更新 很多小伙伴在用 IIS 发布的时候,总是会有一些问题,文章下边 #autoid-6-0-0 我也简单的动图展示了,如何 publish 到 IIS 的过程,如果你能看懂,却发现自己的项目有问题的 ...
- 可能比文档还详细--VueRouter完全指北
可能比文档还详细--VueRouter完全指北 前言 关于标题,应该算不上是标题党,因为内容真的很多很长很全面.主要是在官网的基础上又详细总结,举例了很多东西.确保所有新人都能理解!所以实际上很多东西 ...
- Android webservice的用法详细讲解
Android webservice的用法详细讲解 看到有很多朋友对WebService还不是很了解,在此就详细的讲讲WebService,争取说得明白吧.此文章采用的项目是我毕业设计的webserv ...
随机推荐
- python程序之profile分析
操作系统 : CentOS7.3.1611_x64 python版本:2.7.5 问题描述 1.Python开发的程序在使用过程中很慢,想确定下是哪段代码比较慢: 2.Python开发的程序在使用过程 ...
- UWP 手绘视频创作工具技术分享系列
开篇先来说一下写这篇文章的初衷. 初到来画,通读了来画 UWP App 的代码,发现里面确实有很多比较高深的技术点,同时也是有很多问题的,扩展性,耦合,性能,功能等等.于是我们决定从头重构这个产品,做 ...
- python中字符串中一些函数的用法
1..capitalize():字符串的首字母大写: 2..count():字符串中的某个字母的个数: 3..center(50,'-'):对象居中,且左右用'-'补齐: 4..encode():吧字 ...
- java对文件加锁
详见:http://blog.yemou.net/article/query/info/tytfjhfascvhzxcyt208 在对文件操作过程中,有时候需要对文件进行加锁操作,防止其他线程访问该文 ...
- 手机端rem适应
这段时间做了几个手机版的项目,因为没有用框架,所以用rem来做适应,下面就分享一下 //第一种是比较简单的代码 (function(win) { resizeRoot(); function resi ...
- 教程,Python图片转字符堆叠图
Python 图片转字符画 一.实验说明 1. 环境登录 无需密码自动登录, 2. 环境介绍 本实验环境采用带桌面的UbuntuLinux环境,实验中会用到桌面上的程序: LX终端(LXTermina ...
- 201521123061 《Java程序设计》第十四周学习总结
201521123061 <Java程序设计>第十四周学习总结 1. 本周学习总结 1.1 以你喜欢的方式(思维导图或其他)归纳总结多数据库相关内容. 2. 书面作业 1. MySQL数据 ...
- 201521123031 《Java程序设计》第6周学习总结
1. 本周学习总结 1.1 面向对象学习暂告一段落,请使用思维导图,以封装.继承.多态为核心概念画一张思维导图,对面向对象思想进行一个总结. 注1:关键词与内容不求多,但概念之间的联系要清晰,内容覆盖 ...
- 戴建钊 201521123023《Java程序设计》第2周学习总结
1. 本周学习总结 (1)String类:字符串连接"+",以前觉得方便但不知其原理,所以在有大量修改字符串操作的时候用得不亦乐乎,既浪费内存,又减缓效率.现在知道用Stringb ...
- eclipse ide for java ee developers与eclipse ide for java developers有什么区别
前者集成了WTP,可用于j2ee开发,功能更完善