天天看點

AutoMapper快速上手

一.什麼是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的性能

   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);                          
AutoMapper快速上手

    可以看到映射了1萬個對象隻花費了191毫秒.雖然說對象屬性越多映射所下所花費的時間會越長,但是這個性能已經極為OK了

四.AutoMaper的使用

AutoMapper作為一個易用性極強并且簡便的OOM,在使用方面做到了非常簡便,尤其在6.2.0版本之後,基本不需要做什麼配置,即可完成映射。這裡也是以6.2.0版本來做示例

  1. 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為目标類型,參數為源類型,

AutoMapper快速上手

其中屬性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即可映射成功

AutoMapper快速上手

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);      
AutoMapper快速上手

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);      
AutoMapper快速上手

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屬性并沒有進行映射

AutoMapper快速上手

8.泛型類型映射

AutoMapper中可以直接支援開放泛型類型映射,是以不需要建立封閉泛型類型

映射實體模型

AutoMapper快速上手
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; }

   }      

View Code

 映射配置

//初始化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快速上手

并且可以看到,AutoMapper泛型類型映射時支援類型轉換

9.嵌套類型映射

 映射實體模型

AutoMapper快速上手
AutoMapper快速上手
/// <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.繼承映射

  映射實體模型

AutoMapper快速上手
AutoMapper快速上手
/// <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);
}      
AutoMapper快速上手

11.無須配置的Helper類

   此類隻能簡單的進行配置,無法實作複雜變化,不過一般使用則無需配置【此類出處:https://home.cnblogs.com/u/xiadao521/】

AutoMapper快速上手
AutoMapper快速上手
/// <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);
        }
    }      

繼續閱讀