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

寫在前面

  AutoMapper目錄:html

  本篇目錄:ios

  隨着AutoMapper的學習深刻,發現AutoMapper在對象轉換方面(Object-Object Mapping)還蠻強大的,當時使用AutoMapper的場景是DTO與Domin Model相互轉換,因此文章的標題就是這個(標題有誤),其實AutoMapper不止在這方面的轉換,應該是涵蓋全部對象(Object)之間的轉換,上篇主要講AutoMapper的基本轉換使用,本篇能夠定義爲AutoMapper的靈活配置篇。git

  插一句:有時候學習一門技術,尚未學很深刻,發現還有比其更好的,而後就去學習另一門技術,可能到頭來什麼也沒學會、學精,前兩天看一篇C#程序員-你爲什麼不受大公司青睞,其實不是C#很差,而是你沒有學好,就像前幾年討論C#和Java哪一個好同樣,我我的以爲去爭論這些非常無聊,還不如靜下心,好好的學好一門技術,那就是成功的。程序員

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

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

 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命名規則,可是若是仍是按照正常的類型映射轉換就會報下面異常:app

  」AutoMapperMappingException「這個異常咱們在上篇中提到屢次,AutoMapper在類型映射的時候,若是找不到或映射類型不一致都會報這個異常,怎麼辦?天無絕人之路,AutoMapper提供了自定義類型轉換器:ide

 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自定義類型轉換器:ui

 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,除了StructureMap,咱們也可使用微軟提供的Unity進行依賴注入,參考教程:http://www.cnblogs.com/xishuai/p/3670292.html

  上述示例中,咱們在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管理。

  轉換效果:

後記

  示例代碼下載:http://pan.baidu.com/s/1kTwoALT

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

  若是你以爲本篇文章對你有所幫助,請點擊右下部「推薦」,^_^

  參考資料:

相關文章
相關標籤/搜索