AutoMapper快速上手
一.什么是AutoMapper
AutoMapper是一个简单的对象映射框架(OOM),对象映射原理是把一种类型的输入对象转换为不同类型的输出对象,通俗讲就是通过一些约束讲一种类型中数据自动映射到另一数据类型中
二.AutoMapper的好处
以前的时候我们将DTO对象转换为Model对象或者将Model对象转换为DTO对象时,我们必须将每一个属性都手动映射
//源数据对象
var source = new Source
{
Id = ,
Name = "张三"
};
//目标数据对象
var target = new Target
{
Id = source.Id,
Name = source.Name
};
这样情况如果属性过多会导致浪费大量的时间在对象转换中,于是各种OOM框架应时而生,而AutoMapper便是其一,AutoMapper其优势在于易用性与强大型,AutoMapper除了基本的对象映射之外还可以对进行配置各种需要的映射关系(不同属性名称之间的映射,映射之间的类型转换,支持嵌套映射,支持泛型等),AutoMapper最新版本为6.2.2,而AutoMapper在6.2.0版本中又发生了一次巨大改变,使得开发者能更加简单的使用AutoMapper进行工作。下面是AutoMapper的一个简单示例。
//初始化AutoMapper
Mapper.Initialize(config => { });
//源数据对象
var source = new Source
{
Id = ,
Name = "张三"
};
//映射
var target = Mapper.Map<Source, Target>(source);
Console.WriteLine(target.Id);
Console.WriteLine(target.Name);

可以看到我们只需要简单的使用便可以完成两个对象之间的属性映射,开发中极大的省去了枯燥的属性转换.
三,AutoMapper的性能
AutoMapper做为一个被广泛使用的OOM框架,其底层使用的是表达式树来进行映射,所以在性能方面还是比较突出的,下面是我做的一个性能测试
//初始化AutoMapper
Mapper.Initialize(config => { });
//源数据对象
IList<Source> sourceList = new List<Source>();
for (int i = ; i < ; i++)
{//创建1万个对象进行映射
sourceList.Add(new Source
{
Id = i,
Name = "张三" + i
});
}
Stopwatch watch = new Stopwatch();
watch.Start();
//映射
var targetList = Mapper.Map<IList<Source>, IList<Target>>(sourceList);
watch.Stop();
Console.WriteLine("映射1万个对象的时间为:"+watch.ElapsedMilliseconds);

可以看到映射了1万个对象只花费了191毫秒.虽然说对象属性越多映射所下所花费的时间会越长,但是这个性能已经极为OK了
四.AutoMaper的使用
AutoMapper作为一个易用性极强并且简便的OOM,在使用方面做到了非常简便,尤其在6.2.0版本之后,基本不需要做什么配置,即可完成映射。这里也是以6.2.0版本来做示例
- 1. 引入AutoMapper
 
AutoMapper类库直接可以从NuGit包中引用
install-package automapper -v 6.2.
2.初始化
映射类型
/// <summary>
/// 源类型
/// </summary>
class Source
{
public int Id { get; set; }
public String SName { get; set; }
public String DateTime { get; set; }
public int Age { get; set; } }
/// <summary>
/// 目标类型
/// </summary>
class Target
{
public int Id { get; set; }
public String TName { get; set; }
public String DateTime { get; set; }
public int Age { get; set; }
}
/// <summary>
/// 源类型
/// </summary>
class Source
{
public int Id { get; set; }
public String SName { get; set; }
public String DateTime { get; set; }
public int Age { get; set; } }
/// <summary>
/// 目标类型
/// </summary>
class Target
{
public int Id { get; set; }
public String TName { get; set; }
public String DateTime { get; set; }
public int Age { get; set; }
}
Mapper.Initialize()方法执行AutoMapper的初始化操作,此操作在一个应用程序中只能执行一次.在初始化方法中可以初始化映射中的任何操作
注意:6.20版本之前必须在在配置中设置CreateMap才能映射,6.2.0版本开始如果不进行配置其它则可以省略,但是如果省略CreateMap后默认会以Target类型为基准,如果Target类型有未映射的属性,就会出现异常,加上CreateMap后就无异常,所以推荐手动加上映射配置,以防异常
2.映射
var source = new Source { Id = , SName = "张三", Age = , DateTime = "2018-4-23" };
//执行映射
var target = Mapper.Map<Source, Target>(source);
Console.WriteLine(target.Id);
Console.WriteLine(target.TName);
Console.WriteLine(target.Age);
Console.WriteLine(target.DateTime);
Mapper.Map<S,T> 执行映射方法 S为源类型,T为目标类型,参数为源类型,

其中属性TName因为没找到同名属性,所以并没有映射成功,另外发现源类型中DateTime字符串也成功映射成为目标类型的DateTime,自动类型转换。自动类型转换是6.2.0版本才加入的,在之前需要在配置中进行配置
3.反向映射
在AutoMapper中有一个方法配置是可以配置可以反向映射的, ReverseMap().
//初始化AutoMapper
Mapper.Initialize(config =>
{
//Initialize方法为AutoMapper初始化方法
//6.2.0版本后如果不需要额外的配置,则CreateMap可省略,但6.2.0版本之前不可省略【不过不建议省略】
//ReverseMap方法可以实现反向映射
config.CreateMap<Source, Target>().ReverseMap();
});
var source = new Source { Id = , SName = "张三", Age = , DateTime = "2018-4-23" };
//执行映射
var target = Mapper.Map<Source, Target>(source);
Console.WriteLine(target.Id);
Console.WriteLine(target.TName);
Console.WriteLine(target.Age);
Console.WriteLine(target.DateTime);
Console.WriteLine();
//反向映射
var reverSource = Mapper.Map<Target, Source>(target);
Console.WriteLine(reverSource.Id);
Console.WriteLine(reverSource.SName);
Console.WriteLine(reverSource.Age);
Console.WriteLine(reverSource.DateTime);
注意:ReverseMap也可以不加,但是那样就跟没有配置一样,所以在目标类型中属性没有全部映射完毕情况会出异常,所以还是建议手动配置
4属性名称不一致之间的映射
属性名称不一致之间的映射需要在初始化时进行配置相应属性名称
//初始化AutoMapper
Mapper.Initialize(config =>
{
//Initialize方法为AutoMapper初始化方法
//6.2.0版本后如果不需要额外的配置,则CreateMap可省略,但6.2.0版本之前不可省略【不过不建议省略】
config.CreateMap<Source, Target>()
//ForMember可以配置一系列的配置信息
//参数1:目标类型属性的表达式
//参数2:执行操作的选择 AutoMapper定义了一系列的配置选择供开发者使用
.ForMember(dest=>dest.TName,options=>options.MapFrom(sou=>sou.SName));
});
var source = new Source { Id = , Age = , DateTime = "2018-4-23" };
//执行映射
var target = Mapper.Map<Source, Target>(source);
Console.WriteLine(target.Id);
Console.WriteLine(target.TName);
Console.WriteLine(target.Age);
Console.WriteLine(target.DateTime);
此时目标类型的TName即可映射成功

5.空值替换
AutoMapper中允许设置一个备用值来代替源类型中的空值
//初始化AutoMapper
Mapper.Initialize(config =>
{
//Initialize方法为AutoMapper初始化方法
//6.2.0版本后如果不需要额外的配置,则CreateMap可省略,但6.2.0版本之前不可省略【不过不建议省略】
config.CreateMap<Source, Target>()
//ForMember可以配置一系列的配置信息
//参数1:目标类型属性的表达式
//参数2:执行操作的选择 AutoMapper定义了一系列的配置选择供开发者使用
.ForMember(dest => dest.TName, options => options.MapFrom(sou => sou.SName))
//NullSubstitute是空值替换的配置操作
.ForMember(dest => dest.TName, options => options.NullSubstitute("备用值"));
});
执行映射
var source = new Source { Id = , Age = , DateTime = "2018-4-23" };
//执行映射
var target = Mapper.Map<Source, Target>(source);

6.映射之前与之后操作
AutoMapper可以在映射前后定义一系列的逻辑操作,,使用到的两个方法是BeforeMap和AfterMap
//初始化AutoMapper
Mapper.Initialize(config =>
{
//Initialize方法为AutoMapper初始化方法
//6.2.0版本后如果不需要额外的配置,则CreateMap可省略,但6.2.0版本之前不可省略【不过不建议省略】
config.CreateMap<Source, Target>()
//ForMember可以配置一系列的配置信息
//参数1:目标类型属性的表达式
//参数2:执行操作的选择 AutoMapper定义了一系列的配置选择供开发者使用
.ForMember(dest => dest.TName, options => options.MapFrom(sou => sou.SName))//映射之前操作【将源类型Age值+10】
//BeforMap和AfterMap需要一个Action<TSource,TDestination>参数
.BeforeMap((sou, dest) =>
{
sou.Age += ;
})
//映射之后操作【将目标类型Age值+10】
.AfterMap((sou, dest) =>
{
dest.Age += ;
});
});
执行映射
var source = new Source { Id = , Age = , DateTime = "2018-4-23" };
//执行映射
var target = Mapper.Map<Source, Target>(source);
Console.WriteLine("Id:"+target.Id);
Console.WriteLine("TName:"+target.TName);
Console.WriteLine("Age:"+target.Age);
Console.WriteLine("DateTime"+target.DateTime);

7.条件映射
AutoMapper中可以设置条件映射,即满足指定条件才允许映射,条件映射使用的方法是Condition
//初始化AutoMapper
Mapper.Initialize(config =>
{
//Initialize方法为AutoMapper初始化方法
//6.2.0版本后如果不需要额外的配置,则CreateMap可省略,但6.2.0版本之前不可省略【不过不建议省略】
config.CreateMap<Source, Target>()
//设置属性的映射条件【Age不大于10即不映射】
.ForMember(dest => dest.Age, options => options.Condition(sou => sou.Age > ));
});
var source = new Source { Id = , Age = , DateTime = "2018-4-23" };
//执行映射
var target = Mapper.Map<Source, Target>(source);
可以看到Age属性并没有进行映射

8.泛型类型映射
AutoMapper中可以直接支持开放泛型类型映射,所以不需要创建封闭泛型类型
映射实体模型
/// <summary>
/// 源类型
/// </summary>
class Source<T>
{
public int Id { get; set; }
public String SName { get; set; }
public T SPro { get; set; }
public String DateTime { get; set; }
public int Age { get; set; } }
/// <summary>
/// 目标类型
/// </summary>
class Target<T>
{
public int Id { get; set; }
public String TName { get; set; }
public T TPro { get; set; }
public String DateTime { get; set; }
public int Age { get; set; } }
映射配置
//初始化AutoMapper
Mapper.Initialize(config =>
{
//Initialize方法为AutoMapper初始化方法
//6.2.0版本后如果不需要额外的配置,则CreateMap可省略,但6.2.0版本之前不可省略【不过不建议省略】//泛型类型的映射,AutoMapper允许直接支持开放类型
config.CreateMap(typeof(Source<>), typeof(Target<>))
//泛型中配置条件【由于是开放类型,所以只能使用属性名称字符串】
.ForMember("TName", options => options.MapFrom("SName"))
//空值替换
.ForMember("TName", options => options.NullSubstitute("备用值"))
.ForMember("TPro", option => option.MapFrom("SPro"));
});
var source = new Source<String> { Id = ,SPro="", Age = , DateTime = "2018-4-23" };
//执行映射
var target = Mapper.Map<Source<String>, Target<int>>(source);
Console.WriteLine("Id:"+target.Id);
Console.WriteLine("TPro:"+target.TPro);
Console.WriteLine("TName:"+target.TName);
Console.WriteLine("Age:"+target.Age);
Console.WriteLine("DateTime"+target.DateTime);

并且可以看到,AutoMapper泛型类型映射时支持类型转换
9.嵌套类型映射
映射实体模型
/// <summary>
/// 源类型
/// </summary>
class Source
{
public int Id { get; set; }
public InnerSource InnerSource { get; set; } }
/// <summary>
/// 目标类型
/// </summary>
class Target
{
public int Id { get; set; }
//例1
//public InnerSource InnerTarget { get; set; }
//例2
//public InnerTarget InnerTarget { get; set; } }
/// <summary>
/// 内部源类型
/// </summary>
class InnerSource
{
public int InnerId { get; set; }
public String InnerName { get; set; }
}
/// <summary>
/// 内部目标类型
/// </summary>
class InnerTarget
{
public int InnerId { get; set; }
public String InnerName { get; set; }
}
AutoMapper嵌套类型映射其实就是相当于2对类型的映射.所以配置跟前面配置是一样的.
如果目标类型中的嵌套类型跟源类型中的嵌套类型是同一类型,如目标类型中例1,那么就直接可以映射,
//初始化AutoMapper
Mapper.Initialize(config =>
{
//Initialize方法为AutoMapper初始化方法
//6.2.0版本后如果不需要额外的配置,则CreateMap可省略,但6.2.0版本之前不可省略【不过不建议省略】
config.CreateMap<Source, Target>()
.ForMember(dest => dest.InnerTarget, options => options.MapFrom(sou => sou.InnerSource));
});
var source = new Source { Id = ,InnerSource = new InnerSource { InnerId=,InnerName="内部名称"} };
//执行映射
var target = Mapper.Map<Source, Target>(source);
Console.WriteLine("Id:"+target.Id); Console.WriteLine("InnerTarget.Id:"+target.InnerTarget.InnerId);
Console.WriteLine("InnerTarget.InnerName:" + target.InnerTarget.InnerName);
如果目标类型中嵌套类型与源类型的嵌套类型不是同一类型,如例2,只需配置一下嵌套类型的映射即可.
//初始化AutoMapper
Mapper.Initialize(config =>
{
//Initialize方法为AutoMapper初始化方法
//6.2.0版本后如果不需要额外的配置,则CreateMap可省略,但6.2.0版本之前不可省略【不过不建议省略】
//ReverseMap方法可以实现反向映射
//配置嵌套类型映射
config.CreateMap<InnerSource, InnerTarget > ();
config.CreateMap<Source, Target>()
.ForMember(dest => dest.InnerTarget, options => options.MapFrom(sou => sou.InnerSource));
});
注意:嵌套类型的映射也可以不配置,但是不配置如果目标类型属性没有全部映射完成,也是会报异常.所以并不推荐
10.继承映射
映射实体模型
/// <summary>
/// 源类型
/// </summary>
class Source
{
public int Id { get; set; }
public String Name { get; set; } }
/// <summary>
/// 目标类型
/// </summary>
class Target
{
public int Id { get; set; }
public String Name { get; set; } }
/// <summary>
/// 子类源类型
/// </summary>
class SonSource:Source
{
public int SonId { get; set; }
public String SonName { get; set; }
}
/// <summary>
/// 子类目标类型
/// </summary>
class SonTarget:Target
{
public int SonId { get; set; }
public String SonName { get; set; }
}
AutoMapper支持以多态形式继承映射,继承映射以Include(父填子) 或InculdeBase(子填父)。
//初始化AutoMapper
Mapper.Initialize(config =>
{
//Initialize方法为AutoMapper初始化方法
//6.2.0版本后如果不需要额外的配置,则CreateMap可省略,但6.2.0版本之前不可省略【不过不建议省略】
config.CreateMap<Source, Target>()
//配置派生类的映射【此处是父填子示例,子填父也同理】
.Include<SonSource, SonTarget>();
//配置映射【派生类必须配置】
config.CreateMap<SonSource, SonTarget>();
});
执行映射
//源对象
IList<Source> sourceList = new List<Source>
{
new Source{Id=,Name="Source1"},
new SonSource{SonId=,SonName="SonSource1",Id=,Name="Source2"},
new Source{Id=,Name="Source3"},
};
//映射
var targetList = Mapper.Map<IList<Source>, IList<Target>>(sourceList);
foreach (var item in targetList)
{
//转换为子类
SonTarget son = item as SonTarget;
if (null != son)
Console.WriteLine("编号:" + son.Id + "名称:" + son.Name + "子类编号:" + son.SonId + "子类名称:" + son.SonName);
else
Console.WriteLine("编号:" + item.Id + "名称:" + item.Name);
}

11.无须配置的Helper类
此类只能简单的进行配置,无法实现复杂变化,不过一般使用则无需配置【此类出处:https://home.cnblogs.com/u/xiadao521/】
/// <summary>
/// 对象映射
/// </summary>
public static class Extensions
{
/// <summary>
/// 同步锁
/// </summary>
private static readonly object Sync = new object(); /// <summary>
/// 将源对象映射到目标对象
/// </summary>
/// <typeparam name="TSource">源类型</typeparam>
/// <typeparam name="TDestination">目标类型</typeparam>
/// <param name="source">源对象</param>
/// <param name="destination">目标对象</param>
public static TDestination MapTo<TSource, TDestination>(this TSource source, TDestination destination)
{
return MapTo<TDestination>(source, destination);
} /// <summary>
/// 将源对象映射到目标对象
/// </summary>
/// <typeparam name="TDestination">目标类型</typeparam>
/// <param name="source">源对象</param>
public static TDestination MapTo<TDestination>(this object source) where TDestination : new()
{
return MapTo(source, new TDestination());
} /// <summary>
/// 将源对象映射到目标对象
/// </summary>
private static TDestination MapTo<TDestination>(object source, TDestination destination)
{
if (source == null)
throw new ArgumentNullException(nameof(source));
if (destination == null)
throw new ArgumentNullException(nameof(destination));
var sourceType = GetType(source);
var destinationType = GetType(destination);
var map = GetMap(sourceType, destinationType);
if (map != null)
return Mapper.Map(source, destination);
lock (Sync)
{
map = GetMap(sourceType, destinationType);
if (map != null)
return Mapper.Map(source, destination);
InitMaps(sourceType, destinationType);
}
return Mapper.Map(source, destination);
} /// <summary>
/// 获取类型
/// </summary>
private static Type GetType(object obj)
{
var type = obj.GetType();
if ((obj is System.Collections.IEnumerable) == false)
return type;
if (type.IsArray)
return type.GetElementType();
var genericArgumentsTypes = type.GetTypeInfo().GetGenericArguments();
if (genericArgumentsTypes == null || genericArgumentsTypes.Length == )
throw new ArgumentException("泛型类型参数不能为空");
return genericArgumentsTypes[];
} /// <summary>
/// 获取映射配置
/// </summary>
private static TypeMap GetMap(Type sourceType, Type destinationType)
{
try
{
return Mapper.Configuration.FindTypeMapFor(sourceType, destinationType);
}
catch (InvalidOperationException)
{
lock (Sync)
{
try
{
return Mapper.Configuration.FindTypeMapFor(sourceType, destinationType);
}
catch (InvalidOperationException)
{
InitMaps(sourceType, destinationType);
}
return Mapper.Configuration.FindTypeMapFor(sourceType, destinationType);
}
}
} /// <summary>
/// 初始化映射配置
/// </summary>
private static void InitMaps(Type sourceType, Type destinationType)
{
try
{
var maps = Mapper.Configuration.GetAllTypeMaps();
Mapper.Initialize(config => {
ClearConfig();
foreach (var item in maps)
config.CreateMap(item.SourceType, item.DestinationType);
config.CreateMap(sourceType, destinationType);
});
}
catch (InvalidOperationException)
{
Mapper.Initialize(config => {
config.CreateMap(sourceType, destinationType);
});
}
} /// <summary>
/// 清空配置
/// </summary>
private static void ClearConfig()
{
var typeMapper = typeof(Mapper).GetTypeInfo();
var configuration = typeMapper.GetDeclaredField("_configuration");
configuration.SetValue(null, null, BindingFlags.Static, null, CultureInfo.CurrentCulture);
} /// <summary>
/// 将源集合映射到目标集合
/// </summary>
/// <typeparam name="TDestination">目标元素类型,范例:Sample,不要加List</typeparam>
/// <param name="source">源集合</param>
public static List<TDestination> MapToList<TDestination>(this System.Collections.IEnumerable source)
{
return MapTo<List<TDestination>>(source);
}
}
AutoMapper快速上手的更多相关文章
- 【Python五篇慢慢弹】快速上手学python
		
快速上手学python 作者:白宁超 2016年10月4日19:59:39 摘要:python语言俨然不算新技术,七八年前甚至更早已有很多人研习,只是没有现在流行罢了.之所以当下如此盛行,我想肯定是多 ...
 - 快速上手Unity原生Json库
		
现在新版的Unity(印象中是从5.3开始)已经提供了原生的Json库,以前一直使用LitJson,研究了一下Unity用的JsonUtility工具类的使用,发现使用还挺方便的,所以打算把项目中的J ...
 - [译]:Xamarin.Android开发入门——Hello,Android Multiscreen快速上手
		
原文链接:Hello, Android Multiscreen Quickstart. 译文链接:Hello,Android Multiscreen快速上手 本部分介绍利用Xamarin.Androi ...
 - [译]:Xamarin.Android开发入门——Hello,Android快速上手
		
返回索引目录 原文链接:Hello, Android_Quickstart. 译文链接:Xamarin.Android开发入门--Hello,Android快速上手 本部分介绍利用Xamarin开发A ...
 - 快速上手seajs——简单易用Seajs
		
快速上手seajs——简单易用Seajs 原文 http://www.cnblogs.com/xjchenhao/p/4021775.html 主题 SeaJS 简易手册 http://yslo ...
 - Git版本控制Windows版快速上手
		
说到版本控制,之前用过VSS,SVN,Git接触不久,感觉用着还行.写篇博文给大家分享一下使用Git的小经验,让大家对Git快速上手. 说白了Git就是一个控制版本的工具,其实没想象中的那么复杂,咱在 ...
 - Objective-C快速上手
		
最近在开发iOS程序,这篇博文的内容是刚学习Objective-C时做的笔记,力图达到用最短的时间了解OC并使用OC.Objective-C是OS X 和 iOS平台上面的主要编程语言,它是C语言的超 ...
 - Netron开发快速上手(二):Netron序列化
		
Netron是一个C#开源图形库,可以帮助开发人员开发出类似Visio的作图软件.本文继前文”Netron开发快速上手(一)“讨论如何利用Netron里的序列化功能快速保存自己开发的图形对象. 一个用 ...
 - Netron开发快速上手(一):GraphControl,Shape,Connector和Connection
		
版权所有,引用请注明出处:<<http://www.cnblogs.com/dragon/p/5203663.html >> 本文所用示例下载FlowChart.zip 一个用 ...
 
随机推荐
- 【代码笔记】Web-Javascript-Javascript函数
			
一,效果图. 二,代码. <!DOCTYPE html> <html> <head> <meta charset="utf-8"> ...
 - django(python manage.py imgrate)同步数据库出错后的解决办法
			
问题 很多情况下,因为app的models.py的文件内容有误,但是通过python manage.py check检查不出来时,当执行python manage.py migra ...
 - 关于处理注册表权限无法修改的问题(无法打开主键或注册表项unknown)
			
CMD下(管理员) secedit /configure /cfg %windir%\inf\defltbase.inf /db defltbase.sdb /verbose 此命令可以生成报告, 任 ...
 - Android string.xml 添加特殊字符
			
解决项目中在string.xml 中显示特殊符号的问题,如@号冒号等.只能考虑使用ASCII码进行显示: @号 @ :号 : 空格 以下为常见的ASCII十进制交换编码: --> <- ...
 - Javac编译原理   《深入分析java web 技术内幕》第四章
			
javac编译的四个主要的流程: 词法分析器:将源码转换为Token流 将源代码划分成一个个Token(找出java语言中的关键字) 语法分析器:将Token流转化为语法树 将上述的一个个Token组 ...
 - PyCharm 如何新建Django工程项目
			
声明:本文使用的IDE是PyCharm 2018.1.2版. 一.File-New Project 二.选择工程类型 强烈建议选项"Pure Python",因为如果选择" ...
 - PE文件基础
			
① PE (Portable Executable):微软参考COFF(Common Object File Format)规范,在Windows NT系统上制定的一种标准, 用于exe可执行文件.o ...
 - Scala并发编程【快速入门】
			
1.简介 Scala的actor提供了一种基于事件的轻量级线程.只要使用scala.actors.Actor伴生对象的actor()方法,就可以创建一个actor.它接受一个函数值/闭包做参数,一创建 ...
 - Linux进程上下文切换过程context_switch详解--Linux进程的管理与调度(二十一)
			
1 前景回顾 1.1 Linux的调度器组成 2个调度器 可以用两种方法来激活调度 一种是直接的, 比如进程打算睡眠或出于其他原因放弃CPU 另一种是通过周期性的机制, 以固定的频率运行, 不时的检测 ...
 - Unity Remote 无法连接
			
前言 Unity Remote支持把手机的以下数据返回到Unity Editor中: 触摸输入 加速计 陀螺仪 摄像头 GPS 我的操作环境: Unity 5.3.6f1 在windows 下 And ...