一.什么是AutoMapper
AutoMapper是一個簡單的對象映射框架(OOM),對象映射原理是把一種類型的輸入對象轉換為不同類型的輸出對象,通俗講就是通過一些約束講一種類型中數據自動映射到另一數據類型中
二.AutoMapper的好處
以前的時候我們將DTO對象轉換為Model對象或者將Model對象轉換為DTO對象時,我們必須將每一個屬性都手動映射
//源數據對象 var source = new Source { Id = 1, 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 = 1, 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 = 0; i < 10000; 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.0
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 = 1, SName = "張三", Age = 11, 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 = 1, SName = "張三", Age = 11, 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 = 1, Age = 11, 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 = 1, Age = 11, 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 += 10; }) //映射之后操作【將目標類型Age值+10】 .AfterMap((sou, dest) => { dest.Age += 10; }); });
執行映射
var source = new Source { Id = 1, Age = 11, 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 > 10)); });
var source = new Source { Id = 1, Age = 10, 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 = 1,SPro="1", Age = 10, 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 = 1,InnerSource = new InnerSource { InnerId=11,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=1,Name="Source1"}, new SonSource{SonId=1,SonName="SonSource1",Id=2,Name="Source2"}, new Source{Id=3,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 == 0) throw new ArgumentException("泛型類型參數不能為空"); return genericArgumentsTypes[0]; } /// <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); } }
