AutoMapper目录:
本篇目录:
随着AutoMapper的学习深入,发现AutoMapper在对象转换方面(Object-Object Mapping)还蛮强大的,当时使用AutoMapper的场景是DTO与Domin Model相互转换,所以文章的标题就是这个(标题有误),其实AutoMapper不止在这方面的转换,应该是涵盖所有对象(Object)之间的转换,上篇主要讲AutoMapper的基本转换使用,本篇可以定义为AutoMapper的灵活配置篇。
插一句:有时候学习一门技术,还没有学很深入,发现还有比其更好的,然后就去学习另外一门技术,可能到头来什么也没学会、学精,前两天看一篇C#程序员-你为何不受大公司青睐,其实不是C#不好,而是你没有学好,就像前几年讨论C#和java哪个好一样,我个人觉得去争论这些很是无聊,还不如静下心,好好的学好一门技术,那就是成功的。
在上篇中,我们使用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 destination13 // type14 //15 // 参数:16 // mappingFunction:17 // Callback to convert from source type to destination type18 void ConvertUsing(Func<TSource, TDestination> mappingFunction);19 //20 // 摘要:21 // Skip member mapping and use a custom type converter instance to convert to22 // the destination type23 //24 // 参数:25 // converter:26 // Type converter instance27 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 // 配置 AutoMapper11 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 // 执行 mapping18 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),因为类型转换我们说了算,在业务场景中,我们可以定义一组自定义转换配置器,这样就不需要再做额外的配置,就可以完成想要的类型转换。
上面讲了自定义类型转换器,针对的是不同类型之间映射处理,有这样一种场景:领域模型到DTO的转换,DTO并不是和领域模型之间完全一样,而且还要根据具体的业务场景做一些处理,什么意思?比如我们要对DTO做一些测试或其他一些数据操作(如记录日志时间等),但是和业务无关,如果把这种操作放在领域模型中就有点不伦不类了,所以要在DTO转换中去做,比如下面场景:
1 public class Source2 {3 public int Value1 { get; set; }4 public int Value2 { get; set; }5 }6 public class Destination7 {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 options11 IResolverConfigurationExpression<TSource, TValueResolver> ResolveUsing<TValueResolver>() where TValueResolver : IValueResolver;12 //13 // 摘要:14 // Resolve destination member using a custom value resolver callback. Used instead15 // of MapFrom when not simply redirecting a source member access both the source16 // object and current resolution context for additional mapping, context items17 // and parent objects This method cannot be used in conjunction with LINQ query18 // projection19 //20 // 参数:21 // resolver:22 // Callback function to resolve against source type23 void ResolveUsing(Func<ResolutionResult, object> resolv
新闻热点
疑难解答