AutoMapper用法(转载)
申明
本文转载自http://www.qeefee.com/article/automapper
作者:齐飞
配置AutoMapper映射规则
AutoMapper是基于约定的,因此在实用映射之前,我们需要先进行映射规则的配置。
1 public class Source 2 { 3 public int SomeValue { get; set; } 4 public string AnotherValue { get; set; } 5 } 6 7 public class Destination 8 { 9 public int SomeValue { get; set; } 10 }
在上面的代码中,我们定义了两个类,我们需要将Source类的对象映射到Destination类的对象上面。要完成这个操作,我们需要对AutoMapper进行如下配置:
1 Mapper.CreateMap<Source, Destination>();
进行一下测试:
1 Source src = new Source() { SomeValue = 1, AnotherValue = "2" }; 2 Destination dest = Mapper.Map<Destination>(src); 3 ObjectDumper.Write(dest);
我们可以在控制台看到dest对象的属性值:
这样我们就完成了一个简单的AutoMapper映射。
Profile的用法
Profile提供了一个命名的映射类,所有继承自Profile类的子类都是一个映射集合。
我们来看一下Profile的用法,这个例子中仍然使用上面的Source类和Destination类。
1 public class SourceProfile : Profile 2 { 3 protected override void Configure() 4 { 5 CreateMap<Source, Destination>(); 6 } 7 }
我们可以再Profile中重写Configure方法,从而完成映射规则的配置。从Profile初始化Mapper规则:
1 Mapper.Initialize(x => x.AddProfile<SourceProfile>());
在一个Profile中,我们可以完成多个、更复杂的规则的约定:
1 public class Destination2 2 { 3 public int SomeValue { get; set; } 4 public string AnotherValue2 { get; set; } 5 } 6 7 public class SourceProfile : Profile 8 { 9 protected override void Configure() 10 { 11 //Source->Destination 12 CreateMap<Source, Destination>(); 13 14 //Source->Destination2 15 CreateMap<Source, Destination2>().ForMember(d => d.AnotherValue2, opt => 16 { 17 opt.MapFrom(s => s.AnotherValue); 18 }); 19 } 20 }
AutoMapper最佳实践
这段内容将讨论AutoMapper的规则写在什么地方的问题。
在上一段中,我们已经知道了如何使用AutoMapper进行简单的对象映射,但是,在实际的项目中,我们会有很多类进行映射(从Entity转换为Dto,或者从Entity转换为ViewModel等),这么多的映射如何组织将成为一个问题。
首先我们需要定义一个Configuration.cs的类,该类提供AutoMapper规则配置的入口,它只提供一个静态的方法,在程序第一次运行的时候调用该方法完成配置。
当有多个Profile的时候,我们可以这样添加:
1 public class Configuration 2 { 3 public static void Configure() 4 { 5 Mapper.Initialize(cfg => 6 { 7 cfg.AddProfile<Profiles.SourceProfile>(); 8 cfg.AddProfile<Profiles.OrderProfile>(); 9 cfg.AddProfile<Profiles.CalendarEventProfile>(); 10 }); 11 } 12 }
在程序运行的时候,只需要调用Configure方法即可。
了解了这些实现以后,我们可以再项目中添加AutoMapper文件夹,文件夹结构如下:
Configuration为我们的静态配置入口类;Profiles文件夹为我们所有Profile类的文件夹。如果是MVC,我们需要在Global中调用:
1 AutoMapper.Configuration.Configure();
扁平化映射(Flattening)
默认情况下,我们的Source类和Destination类是根据属性名称进行匹配映射的。除此之外,默认的映射规则还有下面两种情况,我们称之为扁平化映射,即当Source类中不包含Destination类中的属性的时候,AutoMapper会将Destination类中的属性进行分割,或匹配“Get”开头的方法,例如:
Order类:
1 public class Order 2 { 3 public Customer Customer { get; set; } 4 5 public decimal GetTotal() 6 { 7 return 100M; 8 } 9 }
Order类中包含了一个customer对象和一个GetTotal方法,为了方便演示,我直接将GetTotal方法返回100;
Customer类的定义如下:
1 public class Customer 2 { 3 public string Name { get; set; } 4 }
OrderDto类的定义如下:
1 public class OrderDto 2 { 3 public string CustomerName { get; set; } 4 public string Total { get; set; } 5 }
我们在进行映射的时候,不需要进行特殊的配置,既可以完成从Order到OrderDto的映射。
1 public class OrderProfile : Profile 2 { 3 protected override void Configure() 4 { 5 CreateMap<Entity.Order, Dto.OrderDto>(); 6 } 7 }
测试代码:
1 Entity.Customer customer = new Entity.Customer() { Name = "Tom" }; 2 Entity.Order order = new Entity.Order() { Customer = customer }; 3 Dto.OrderDto orderDto = Mapper.Map<Dto.OrderDto>(order); 4 ObjectDumper.Write(order, 2); 5 ObjectDumper.Write(orderDto);
测试结果:
指定映射字段(Projection)
在实际的业务环境中,我们的Source类和Destination类的字段不可能一对一的匹配,这个时候我们就需要来指定他们的实际映射关系,例如:
1 public class CalendarEvent 2 { 3 public DateTime Date { get; set; } 4 public string Title { get; set; } 5 } 6 7 public class CalendarEventForm 8 { 9 public DateTime EventDate { get; set; } 10 public int EventHour { get; set; } 11 public int EventMinute { get; set; } 12 public string DisplayTitle { get; set; } 13 }
在这两个类中,CalendarEvent的Date将被拆分为CalendarEventForm的日期、时、分三个字段,Title也将对应DisplayTitle字段,那么相应的Profile定义如下:
1 public class CalendarEventProfile : Profile 2 { 3 protected override void Configure() 4 { 5 CreateMap<Entity.CalendarEvent, Entity.CalendarEventForm>() 6 .ForMember(dest => dest.EventDate, opt => opt.MapFrom(src => src.Date.Date)) 7 .ForMember(dest => dest.EventHour, opt => opt.MapFrom(src => src.Date.Hour)) 8 .ForMember(dest => dest.EventMinute, opt => opt.MapFrom(src => src.Date.Minute)) 9 .ForMember(dest => dest.DisplayTitle, opt => opt.MapFrom(src => src.Title)); 10 } 11 }
测试代码:
1 Entity.CalendarEvent calendarEvent = new Entity.CalendarEvent() 2 { 3 Date = DateTime.Now, 4 Title = "Demo Event" 5 }; 6 Entity.CalendarEventForm calendarEventForm = Mapper.Map<Entity.CalendarEventForm>(calendarEvent); 7 ObjectDumper.Write(calendarEventForm);
测试结果:
验证配置项(Configuration Validation)
AutoMapper提供了一种验证机制,用来判断Destination类中的所有属性是否都被映射,如果存在未被映射的属性,则抛出异常。
验证的用法:
1 Mapper.AssertConfigurationIsValid();
例如:
1 public class Source 2 { 3 public int SomeValue { get; set; } 4 public string AnotherValue { get; set; } 5 }
Destination代码:
1 public class Destination 2 { 3 public int SomeValuefff { get; set; } 4 }
测试:
1 Mapper.CreateMap<Entity.Source, Entity.Destination>(); 2 Mapper.AssertConfigurationIsValid();
运行程序将会出现AutoMapperConfigurationException异常:
这是因为SomeValuefff在Source类中没有对应的字段造成的。
解决这种异常的方法有:
指定映射字段,例如:
1 Mapper.CreateMap<Entity.Source, Entity.Destination>() 2 .ForMember(dest => dest.SomeValuefff, opt => 3 { 4 opt.MapFrom(src => src.SomeValue); 5 });
或者使用Ignore方法:
1 Mapper.CreateMap<Entity.Source, Entity.Destination>() 2 .ForMember(dest => dest.SomeValuefff, opt => 3 { 4 opt.Ignore(); 5 });
或者使用自定义解析器,自定义解析器在下面讲到。
自定义解析器(Custom value resolvers)
AutoMapper允许我们自定义解析器来完成Source到Destination的值的转换。例如:
1 public class Source 2 { 3 public int Value1 { get; set; } 4 public int Value2 { get; set; } 5 } 6 7 public class Destination 8 { 9 public int Total { get; set; } 10 }
Total属性在Source中不存在,如果现在创建映射规则,在映射的时候必然会抛出异常。这个时候我们就需要使用自定义解析器来完成映射。
自定义解析器需要实现 IValueResolver 接口,接口的定义如下:
1 public interface IValueResolver 2 { 3 ResolutionResult Resolve(ResolutionResult source); 4 }
我们来自定义一个Resolver:
1 public class CustomResolver : ValueResolver<Source, int> 2 { 3 protected override int ResolveCore(Source source) 4 { 5 return source.Value1 + source.Value2; 6 } 7 }
然后在映射规则中使用这个解析器:
1 public class SourceProfile : Profile 2 { 3 protected override void Configure() 4 { 5 //Source->Destination 6 CreateMap<Source, Destination>() 7 .ForMember(dest => dest.Total, opt => 8 { 9 opt.ResolveUsing<CustomResolver>(); 10 }); 11 } 12 }
测试代码:
1 Source src = new Source() 2 { 3 Value1 = 1, 4 Value2 = 2 5 }; 6 Destination dest = Mapper.Map<Destination>(src); 7 ObjectDumper.Write(dest);
测试结果:
在使用自定义Resolver中,我们还可以指定Resolver的构造函数,例如:
1 //Source->Destination 2 CreateMap<Source, Destination>() 3 .ForMember(dest => dest.Total, opt => 4 { 5 opt.ResolveUsing<CustomResolver>() 6 .ConstructedBy(() => new CustomResolver()); 7 });
自定义类型转换器(Custom type converters)
AutoMapper通过ConvertUsing来使用自定义类型转换器。ConvertUsing有三种用法:
1 void ConvertUsing(Func<TSource, TDestination> mappingFunction); 2 void ConvertUsing(ITypeConverter<TSource, TDestination> converter); 3 void ConvertUsing<TTypeConverter>() where TTypeConverter : ITypeConverter<TSource, TDestination>;
当我们有如下的Source类和Destination类:
1 public class Source 2 { 3 public string Value1 { get; set; } 4 } 5 6 public class Destination 7 { 8 public int Value1 { get; set; } 9 }
我们可以使用如下配置:
1 public class SourceProfile : Profile 2 { 3 protected override void Configure() 4 { 5 //string->int 6 CreateMap<string, int>() 7 .ConvertUsing(Convert.ToInt32); 8 //Source->Destination 9 CreateMap<Source, Destination>(); 10 } 11 }
在上面的配置中,我们首先创建了从string到int的类型转换,这里使用了系统自带的Convert.ToInt32转换方法。
除了这种方法之外,我们还可以自定义类型转换器:
1 public class CustomConverter : ITypeConverter<Source, Destination> 2 { 3 public Destination Convert(ResolutionContext context) 4 { 5 Source src = context.SourceValue as Source; 6 Destination dest = new Destination(); 7 dest.Value1 = System.Convert.ToInt32(src.Value1); 8 9 return dest; 10 } 11 }
通过这个转换器,我们可以绕过string到int的转换,直接将Source类的对象转换为Destination类的对象。
对应的配置如下:
1 public class SourceProfile : Profile 2 { 3 protected override void Configure() 4 { 5 //Source->Destination 6 CreateMap<Source, Destination>() 7 .ConvertUsing<CustomConverter>(); 8 } 9 }
或者,我们也可以使用下面的配置:
1 public class SourceProfile : Profile 2 { 3 protected override void Configure() 4 { 5 //Source->Destination 6 CustomConverter converter = new CustomConverter(); 7 CreateMap<Source, Destination>() 8 .ConvertUsing(converter); 9 } 10 }
空值替换(Null substitution)
空值替换允许我们将Source对象中的空值在转换为Destination的值的时候,使用指定的值来替换空值。
1 public class Source 2 { 3 public string Value { get; set; } 4 } 5 6 public class Destination 7 { 8 public string Value { get; set; } 9 }
配置代码:
1 public class SourceProfile : Profile 2 { 3 protected override void Configure() 4 { 5 //Source->Destination 6 CreateMap<Source, Destination>() 7 .ForMember(dest => dest.Value, opt => 8 { 9 opt.NullSubstitute("原始值为NULL"); 10 }); 11 } 12 }
测试代码:
1 Source src = new Source(); 2 Destination dest = Mapper.Map<Destination>(src); 3 ObjectDumper.Write(dest);
测试结果:
条件映射(Conditional mapping)
条件映射只当Source类中的属性值满足一定条件的时候才进行映射。例如:
1 public class Foo 2 { 3 public int baz; 4 } 5 6 public class Bar 7 { 8 public uint baz; 9 }
对应的配置代码如下:
1 Mapper.CreateMap<Foo, Bar>() 2 .ForMember(dest => dest.baz, opt => 3 { 4 opt.Condition(src => (src.baz >= 0)); 5 });
转载
作者:齐飞