1. 程式人生 > >【AutoMapper官方文件】DTO與Domin Model相互轉換(中)

【AutoMapper官方文件】DTO與Domin Model相互轉換(中)

寫在前面

  AutoMapper目錄:

  本篇目錄:

  隨著AutoMapper的學習深入,發現AutoMapper在物件轉換方面(Object-Object Mapping)還蠻強大的,當時使用AutoMapper的場景是DTO與Domin Model相互轉換,所以文章的標題就是這個(標題有誤),其實AutoMapper不止在這方面的轉換,應該是涵蓋所有物件(Object)之間的轉換,上篇主要講AutoMapper的基本轉換使用,本篇可以定義為AutoMapper的靈活配置篇。

  插一句:有時候學習一門技術,還沒有學很深入,發現還有比其更好的,然後就去學習另外一門技術,可能到頭來什麼也沒學會、學精,前兩天看一篇

C#程式設計師-你為何不受大公司青睞,其實不是C#不好,而是你沒有學好,就像前幾年討論C#和Java哪個好一樣,我個人覺得去爭論這些很是無聊,還不如靜下心,好好的學好一門技術,那就是成功的。

Custom Type Converters-自定義型別轉換器

  在上篇中,我們使用AutoMapper型別對映轉換,都是相同的型別轉換,比如string轉string、datetime轉datetime,但是有時候在一些複雜的業務場景中,可能會存在跨型別對映轉換,比如我們下面的場景:

 1         public class Source
 2         {
 3             public
string Value1 { get; set; } 4 public string Value2 { get; set; } 5 public string Value3 { get; set; } 6 } 7 public class Destination 8 { 9 public int Value1 { get; set; } 10 public DateTime Value2 { get; set; } 11 public
Type Value3 { get; set; } 12 }

  從程式碼中可以看出,string需要轉換為目標型別:int、DateTime和Type,轉換的型別並不一致,雖然我們命名符合PascalCase命名規則,但是如果還是按照正常的型別對映轉換就會報下面異常:

  ”AutoMapperMappingException“這個異常我們在上篇中提到多次,AutoMapper在型別對映的時候,如果找不到或對映型別不一致都會報這個異常,怎麼辦?天無絕人之路,AutoMapper提供了自定義型別轉換器:

 1         //
 2         // 摘要:
 3         //     Skip member mapping and use a custom type converter instance to convert to
 4         //     the destination type
 5         //
 6         // 型別引數:
 7         //   TTypeConverter:
 8         //     Type converter type
 9         void ConvertUsing<TTypeConverter>() where TTypeConverter : ITypeConverter<TSource, TDestination>;
10         //
11         // 摘要:
12         //     Skip member mapping and use a custom function to convert to the destination
13         //     type
14         //
15         // 引數:
16         //   mappingFunction:
17         //     Callback to convert from source type to destination type
18         void ConvertUsing(Func<TSource, TDestination> mappingFunction);
19         //
20         // 摘要:
21         //     Skip member mapping and use a custom type converter instance to convert to
22         //     the destination type
23         //
24         // 引數:
25         //   converter:
26         //     Type converter instance
27         void ConvertUsing(ITypeConverter<TSource, TDestination> converter);

  ConvertUsing是什麼?它是我們在指定型別對映時,型別配置的方法,就是說通過ConvertUsing方法把型別對映中型別轉換的許可權交給使用者配置,而不是通過AutoMapper進行自動型別轉換,這就給我提供了更多的自定義性,也就避免了不同型別之間轉換而引起的AutoMapperMappingException“異常。

  ConvertUsing三個過載方法,第二個適用於簡單型別轉換,接受一個型別,返回一個目標型別,第一個和第三個其實基本一樣,一個是例項,一個是型別,但都必須是ITypeConverter<TSource, TDestination>泛型介面的派生類。

  正好上面示例中需要對三種類型進行轉換,就分別用ConvertUsing三個過載方法,因為string轉int很簡單就使用第二個過載方法,string轉DateTime、Type自定義型別轉換器:

 1         public class DateTimeTypeConverter : TypeConverter<string, DateTime>
 2         {
 3             protected override DateTime ConvertCore(string source)
 4             {
 5                 return System.Convert.ToDateTime(source);
 6             }
 7         }
 8         public class TypeTypeConverter : TypeConverter<string, Type>
 9         {
10             protected override Type ConvertCore(string source)
11             {
12                 TypeConverter dd = TypeDescriptor.GetConverter(source.GetType());
13                 dd.CanConvertTo(typeof(Type));
14                 Type type = Assembly.GetExecutingAssembly().GetType(source);
15                 return type;
16             }
17         }

  當然業務場景如果複雜的話,可以在轉換器中做些詳細的配置內容,TypeConverter的CanConvertTo方法是判斷相互轉換型別的可行性,不可轉換返回false,除此之外,再列下TypeConverter的幾個常用方法:

CanConvertFrom(Type) 返回該轉換器是否可以將給定型別的物件轉換為此轉換器的型別。
CanConvertFrom(ITypeDescriptorContext, Type) 返回該轉換器是否可以使用指定的上下文將給定型別的物件轉換為此轉換器的型別。
CanConvertTo(Type) 返回此轉換器是否可將該物件轉換為指定的型別。
CanConvertTo(ITypeDescriptorContext, Type) 返回此轉換器是否可以使用指定的上下文將該物件轉換為指定的型別。
ConvertFrom(Object) 將給定值轉換為此轉換器的型別。
ConvertFrom(ITypeDescriptorContext, CultureInfo, Object) 使用指定的上下文和區域性資訊將給定的物件轉換為此轉換器的型別。

  AutoMapper配置轉換程式碼:

 1         public void Example()
 2         {
 3             var source = new Source
 4             {
 5                 Value1 = "5",
 6                 Value2 = "01/01/2000",
 7                 Value3 = "DTO_AutoMapper使用詳解.GlobalTypeConverters+Destination"
 8             };
 9 
10             // 配置 AutoMapper
11             Mapper.CreateMap<string, int>().ConvertUsing(Convert.ToInt32);
12             Mapper.CreateMap<string, DateTime>().ConvertUsing(new DateTimeTypeConverter());
13             Mapper.CreateMap<string, Type>().ConvertUsing<TypeTypeConverter>();
14             Mapper.CreateMap<Source, Destination>();
15             Mapper.AssertConfigurationIsValid();
16 
17             // 執行 mapping
18             Destination result = Mapper.Map<Source, Destination>(source);
19             Console.WriteLine("result.Value1:" + result.Value1.ToString());
20             Console.WriteLine("result.Value2:" + result.Value2.ToString());
21             Console.WriteLine("result.Value3:" + result.Value3.ToString());
22         }

  在自定義轉換配置中雖然配置了轉換型別,但是CreateMap中也需要制定其型別,而且要和轉換器中型別所一致,最後Mapper.CreateMap<Source, Destination>();完成Source到Destination的配置轉換,其實上面的配置器可以看成Source原始型別Destination(目標型別)所依賴型別之間的轉換。轉換效果:

  自定義轉換配置器的強大之處在於,我們可以完成任何型別之間的相互轉換(只要符合CanConvertTo),因為型別轉換我們說了算,在業務場景中,我們可以定義一組自定義轉換配置器,這樣就不需要再做額外的配置,就可以完成想要的型別轉換。

Custom Value Resolvers-自定義值解析器

  上面講了自定義型別轉換器,針對的是不同型別之間對映處理,有這樣一種場景:領域模型到DTO的轉換,DTO並不是和領域模型之間完全一樣,而且還要根據具體的業務場景做一些處理,什麼意思?比如我們要對DTO做一些測試或其他一些資料操作(如記錄日誌時間等),但是和業務無關,如果把這種操作放在領域模型中就有點不倫不類了,所以要在DTO轉換中去做,比如下面場景:

1         public class Source
2         {
3             public int Value1 { get; set; }
4             public int Value2 { get; set; }
5         }
6         public class Destination
7         {
8             public int Total { get; set; }
9         }

  轉換目標物件中我們想得到一個計算值,就是在轉換中對目標值進行解析,如果你看了Projection這一節點,可能覺得很簡單,我們可以使用自定義轉換規則就可以做到:

1 Mapper.CreateMap<Source, Destination>()
2                 .ForMember(dest => dest.Total, opt => opt.MapFrom(src => src.Value1 + src.Value2));

  這種方式雖然可以解決上述場景中的問題,但是不提倡這樣做,如果解析過程複雜一些,或者解析方式經常出現改動,這樣我們維護起來就很麻煩了,所以我們要定義一個值解析器,或者稱為目標值解析器,和上面說的型別轉換器(ConvertUsing)比較類似,AutoMapper提供了ResolveUsing方法用於目標值解析器:

 1         //
 2         // 摘要:
 3         //     Resolve destination member using a custom value resolver
 4         //
 5         // 型別引數:
 6         //   TValueResolver:
 7         //     Value resolver type
 8         //
 9         // 返回結果:
10         //     Value resolver configuration options
11         IResolverConfigurationExpression<TSource, TValueResolver> ResolveUsing<TValueResolver>() where TValueResolver : IValueResolver;
12         //
13         // 摘要:
14         //     Resolve destination member using a custom value resolver callback. Used instead
15         //     of MapFrom when not simply redirecting a source member Access both the source
16         //     object and current resolution context for additional mapping, context items
17         //     and parent objects This method cannot be used in conjunction with LINQ query
18         //     projection
19         //
20         // 引數:
21         //   resolver:
22         //     Callback function to resolve against source type
23         void ResolveUsing(Func<ResolutionResult, object> resolver);
24         //
25         // 摘要:
26         //     Resolve destination member using a custom value resolver callback. Used instead
27         //     of MapFrom when not simply redirecting a source member This method cannot
28         //     be used in conjunction with LINQ query projection
29         //
30         // 引數:
31         //   resolver:
32         //     Callback function to resolve against source type
33         void ResolveUsing(Func<TSource, object> resolver);

  可以看到ResolveUsing方法和ConvertUsing方式比較類似,ResolveUsing方法引數物件必須是抽象類ValueResolver<TSource, TDestination>的派生類,準確的說是介面IValueResolver的派生類,和自定義轉換器一樣,我們要自定義一個目標值解析器:

1         public class CustomResolver : ValueResolver<Source, int>
2         {
3             protected override int ResolveCore(Source source)
4             {
5                 return source.Value1 + source.Value2;
6             }
7         }

  CustomResolver目標值解析器繼承ValueResolver,指定源型別和目標值型別,並重寫ResolveCore抽象方法,返回操作值,AutoMapper配置對映型別轉換:

 1         public void Example()
 2         {
 3             var source = new Source
 4             {
 5                 Value1 = 5,
 6                 Value2 = 7
 7             };
 8 
 9             // 配置 AutoMapper
10             Mapper.CreateMap<Source, Destination>()
11                 .ForMember(dest => dest.Total, opt => opt.ResolveUsing<CustomResolver>());
12             Mapper.AssertConfigurationIsValid();
13             // 執行 mapping
14             var result = Mapper.Map<Source, Destination>(source);
15 
16             Console.WriteLine("result.Total:" + result.Total);
17         }

  轉換效果:

  除了上述使用方式,AutoMapper還提供了自定義構造方法方式,英文原文:“Because we only supplied the type of the custom resolver to AutoMapper, the mapping engine will use reflection to create an instance of the value resolver.If we don't want AutoMapper to use reflection to create the instance, we can either supply the instance directly, or use the ConstructedBy method to supply a custom constructor method.AutoMapper will execute this callback function instead of using reflection during the mapping operation, helpful in scenarios where the resolver might have constructor arguments or need to be constructed by an IoC container.”

  就像上述這段話的最後,我理解的這種方式適用於自定義解析器中存在構造方法引數,或者通過IoC容器來構建,轉換效果和上面那種方式一樣,呼叫示例:

1             // 配置 AutoMapper
2             Mapper.CreateMap<Source, Destination>()
3                 .ForMember(dest => dest.Total,
4                            opt => opt.ResolveUsing<CustomResolver>().ConstructedBy(() => new CustomResolver())
5                 );

Null Substitution-空值替換

  空值替換,顧名思義就是原始值為空,在轉換配置中我們定義替換空值的值,使用NullSubstitute方法,使用方式類似於Ignore方法,只不過Ignore是忽略或不包含的意思,NullSubstitute是為空賦值,接受一個object型別的引數,就是我們要指定替換的值,使用很簡單,貼下示例程式碼:

 1         public class Source
 2         {
 3             public string Value { get; set; }
 4         }
 5         public class Destination
 6         {
 7             public string Value { get; set; }
 8         }
 9         public void Example()
10         {
11             var source = new Source { Value = null };
12 
13             // 配置 AutoMapper
14             Mapper.CreateMap<Source, Destination>()
15                 .ForMember(dest => dest.Value, opt => opt.NullSubstitute("Other Value"));
16             Mapper.AssertConfigurationIsValid();
17             // 執行 mapping
18             var result = Mapper.Map<Source, Destination>(source);
19             Console.WriteLine("result.Value:" + result.Value);
20 
21             source.Value = "Not null";
22             result = Mapper.Map<Source, Destination>(source);
23             Console.WriteLine("result.Value:" + result.Value);
24         }

  第一次轉換源值物件為null,我們指定替換null的值為“Other Value”,並打印出目標型別轉換值,第二次轉換源值物件為“Not null”,配置和第一次轉換一樣,並打印出目標型別轉換值。轉換效果:

Containers-IoC容器

  AutoMapper中擴充套件了關於IoC的應用,這樣使得我們在專案中應用AutoMapper更加靈活多變,但適用於大型專案或者業務場景很複雜的情況下,簡單的專案沒必要這樣做,關於IoC的相關知識可以參照:http://www.cnblogs.com/xishuai/p/3666276.htmlAutoMapper提供了IoC應用的相關示例程式碼,但是有些錯誤,比如在InversionOfControl類檔案第81行:

  應改為:

1             ForRequestedType<ConfigurationStore>()
2                 .CacheBy(InstanceScope.Singleton)
3                 .TheDefault.Is.OfConcreteType<ConfigurationStore>()
4                 .CtorDependency<IEnumerable<IObjectMapper>>().Is(expr => expr.ConstructedBy(() => MapperRegistry.Mappers));

  執行中還有幾個錯誤,比如IoC配置出錯,AutoMapper配置無效等,都是通過AutoMapper提供相關介面進行注入的,不知道是不是配置問題,以後可以再研究下,這邊稍微整理了下,通過Mapper提供的例項進行注入,簡單演示下AutoMapper中IoC的應用。

 1     public class InversionOfControl
 2     {
 3         private class Source
 4         {
 5             public int Value { get; set; }
 6         }
 7         private class Destination
 8         {
 9             public int Value { get; set; }
10         }
11         public void Example2()
12         {
13             //StructureMap初始化,新增配置命令
14             ObjectFactory.Initialize(init =>
15             {
16                 init.AddRegistry<MappingEngineRegistry>();
17             });
18 
19             Mapper.Reset();
20 
21             var configuration = ObjectFactory.GetInstance<IConfiguration>();//返回註冊型別為IConfiguration的物件
22             configuration.CreateMap<Source, Destination>();//相當於Mapper.CreateMap
23 
24             var engine = ObjectFactory.GetInstance<IMappingEngine>();//返回註冊型別為IMappingEngine的物件
25 
26             var destination = engine.Map<Source, Destination>(new Source { Value = 15 });//相當於Mapper.Map
27             Console.WriteLine("destination.Value:" + destination.Value);
28         }
29     }
30 
31     public class MappingEngineRegistry : Registry
32     {
33         public MappingEngineRegistry()
34         {
35             ForRequestedType<IConfiguration>()
36                 .TheDefault.Is.ConstructedBy(() => Mapper.Configuration);
37 
38             ForRequestedType<IMappingEngine>()
39                 .TheDefault.Is.ConstructedBy(() => Mapper.Engine);
40         }
41     }

  程式碼就這麼多,但是可以簡單體現出AutoMapper中IoC的應用,應用IoC使用的是StructureMap,原始碼地址:https://github.com/structuremap/structuremap,使用NuGet安裝StructureMap命令:“Install-Package StructureMap”,也可以直接新增StructureMap.dll,除了

  上述示例中,我們在IoC中添加了兩個型別對映,IConfiguration對應Mapper.Configuration(IConfiguration),IMappingEngine對應Mapper.Engine(IMappingEngine),使用AddRegistry泛型方法在初始化的時候,注入型別對映關係,就像Unity中Configure載入配置檔案一樣,然後通過ObjectFactory.GetInstance方法解析出注入型別的具體物件,示例中使用AutoMapper預設的Configuration和Engine例項,分別繼承於IConfiguration和IMappingEngine介面,通過繼承其介面,我們還可以自定義Configuration和Engine,當然除了IConfiguration和IMappingEngine介面,AutoMapper還提供了其他的介面型別,比如IConfigurationProvider,IProfileConfiguration,IProfileExpression等等,都可以對其進行IoC管理。

  轉換效果:

後記

  貪多嚼不爛,關於AutoMapper的使用先整理這些,後面會陸續更新,還請關注。

  如果你覺得本篇文章對你有所幫助,請點選右下部“推薦”,^_^

  參考資料:

相關推薦

AutoMapper官方DTODomin Model相互轉換

寫在前面   AutoMapper目錄:   本篇目錄:   隨著AutoMapper的學習深入,發現AutoMapper在物件轉換方面(Object-Object Mapping)還蠻強大的,當時使用AutoMapper的場景是DTO與Domin Model相互轉換,所以文章的標題就是這個(標

AutoMapper官方DTODomin Model相互轉換

寫在前面   AutoMapper目錄:   本篇目錄:   上一篇《【道德經】漫談實體、物件、DTO及AutoMapper的使用 》,因為內容寫的有點跑偏,關於AutoMapper的使用最後只是簡單寫了下,很明顯這種簡單的使用方式不能滿足專案中複雜的需要,網上找了下AutoMapper相關文件

AutoMapper官方DTODomin Model相互轉換

寫在前面   AutoMapper目錄:   本篇目錄:   關於AutoMapper寫到這基本的東西都差不多了,上一篇定義為靈活配置篇,本篇可以定義為擴充套件應用篇,加一些補充,關於AutoMapper的專案應用,網上找了幾篇英文文章,雖然看不懂,但是程式碼是相通的,感覺很不錯,主要是Enti

android官方android AIDL

概述      AIDL(安卓介面解釋語言)和其他的IDLs類似。可以定義程式介面讓客戶端和service進行跨程序的通訊(IPC)。在android中,一個程序通常不能訪問另一個程序的記憶體。所以,他們的物件需要被分解成更原始的單位,直到系統可以理解,並且集結這些物件穿

Android官方翻譯Android官方-Activities(一)

Activity是可以給使用者提供互動操作的程式元件,例如打電話,拍照,傳送郵件,抑或者是顯示地圖。通常視窗會填滿螢幕,但是也可以做到比螢幕小或者是懸浮在視窗頂部。 App通常由多個Activities組成,它們之間支援相互跳轉。一般情況下,每個Activit

pytest官方解讀fixtures - 1.什麼是fixtures

在深入瞭解fixture之前,讓我們先看看什麼是`測試`。 ### 一、測試的構成 其實說白了,測試就是在特定的環境、特定的場景下、執行特定的行為,然後確認結果與期望的是否一致。 就拿最常見的登入來說,完成一次正常的登入場景,需要可用的測試環境,可以正常登入的賬號和密碼。 然後,用這個賬號密碼進行登入操

pytest官方解讀fixtures - 2. fixtures的呼叫方式

既然fixtures是給執行測試做準備工作的,那麼pytest如何知道哪些測試函式 或者 fixtures要用到哪一個fixtures呢? 說白了,就是fixtures的呼叫。 ### 一、測試函式宣告傳參請求fixture 測試函式通過將fixture宣告為引數來請求fixture。 ``` def te

pytest官方解讀fixtures - 8. yield和addfinalizer的區別填坑

在[上一章](https://www.cnblogs.com/pingguo-softwaretesting/p/14479170.html)中,文末留下了一個坑待填補,疑問是這樣的: 目前從官方文件中看到的是 ``` We have to be careful though, because pytest

Pulsar官方翻譯-入門必看-概念和架構-概覽Pulsar Overview

官網原文標題《Concepts and Architecture--Pulsar Overview》 翻譯時間:2018-09-28 譯者:本文介紹了Pulsar的起源和現狀,以及主要特性。 後續閱讀:《Messaging Concepts》 譯者序言: 由

百度地圖高階例項1-如何利用百度地圖API,製作房產酒店地圖?

<!DOCTYPE html><html><head><meta http-equiv="Content-Type" content="text/html; charset=gb2312"/><title>酷訊酒店地圖</title>

unicodeGB2312的相互轉換js

上回說到,我們用C語言輸出了一張GB2312的全部字元表……同時也說,有了這個,我們就能實現使用js進行unicode和GB2312之間的轉碼了……再加上前回(其實是幾年之前)說到,用js沒有內建函式實現這兩者的轉碼,如果用到,一般都是藉助於vbs……這使得我的BF直譯器(

xmlbean間相互轉換補充

今天x被stream對xmlnode的屬性(attribute)解析的問題一直困擾著,查詢了很久都告知我要手寫一個Converter,那豈不意味著我每解析一個xml檔案,就得寫一次Converter,那樣太腦殘了,最後搜尋到其實可以用註解解決這個問題 XStream常用註解

AutoMapper官方(二)升級指南

初始化 您現在必須使用Mapper.Initialize或new MapperConfiguration()來初始化AutoMapper。如果您希望保持靜態使用,請使用Mapper.Initialize。 如果你有很多的Mapper.CreateMap呼叫,把它們移動到一個Profile,或者Mapper

pythonnumpy庫陣列拼接np.concatenate官方詳解例項

在實踐過程中,會經常遇到陣列拼接的問題,基於numpy庫concatenate是一個非常好用的陣列操作函式。 1、concatenate((a1, a2, …), axis=0)官方文件詳解 concatenate(...) concatenate(

pySerial3.4官方6、示例

示例 Miniterm  Miniterm現在可用作模組而不是示例。有關詳細資訊,請參閱serial.tools.miniterm。 miniterm.py miniterm計劃。 setup-miniterm-py2exe.py 這是Windows的py2exe安

pySerial3.4官方4、工具

工具 serial.tools.list_ports  可以執行此模組以獲取埠列表()。它還包含以下功能。python -m serial.tools.list_ports serial.tools.list_ports.comports(include_l

pySerial3.4官方3、pySerial API

pySerial API  類 本地埠 類serial.Serial __init__(port = None,baudrate = 9600,bytesize = EIGHTBITS,parity = PARITY_NONE,stopbits = STOPBITS_ONE

pySerial3.4官方2、簡介

簡介 開啟串列埠 開啟“9600,8,N,1”的埠,沒有超時: >>> import serial >>> ser = serial.Serial('/dev/ttyUSB0') # open serial port >>> pri

Gradle官方翻譯起步2:建立構建掃描

構建掃描是對構建的可分享的專門記錄,可以看到“構建中發生了那些行為以及為什麼會發生這種行為”。通過在專案中使用構建掃描外掛,開發者可以免費地在https://scans.gradle.com/上釋出構建掃描。 將要建立的 本文會展示如何在不對任何構建指令碼進行

cocos2d-js官方十七、事件分發機制

簡介 遊戲開發中一個很重要的功能就是互動,如果沒有與使用者的互動,那麼遊戲將變成動畫,而處理使用者互動就需要使用事件監聽器了。 總概: 事件監聽器(cc.EventListener) 封裝使用者的事件處理邏輯事件管理器(cc.eventManager) 管理使用者註