Fork me on GitHub

【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,除了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的使用先整理这些,后面会陆续更新,还请关注。

  如果你觉得本篇文章对你有所帮助,请点击右下部“推荐”,^_^

  参考资料:

posted @ 2014-05-04 08:52 田园里的蟋蟀 阅读(...) 评论(...) 编辑 收藏