本文轉載自http://www.qeefee.com/article/automapperapp
做者:齊飛ide
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>();
進行一下測試:ui
1 Source src = new Source() { SomeValue = 1, AnotherValue = "2" }; 2 Destination dest = Mapper.Map<Destination>(src); 3 ObjectDumper.Write(dest);
咱們能夠在控制檯看到dest對象的屬性值:spa
這樣咱們就完成了一個簡單的AutoMapper映射。3d
Profile提供了一個命名的映射類,全部繼承自Profile類的子類都是一個映射集合。code
咱們來看一下Profile的用法,這個例子中仍然使用上面的Source類和Destination類。orm
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進行簡單的對象映射,可是,在實際的項目中,咱們會有不少類進行映射(從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();
默認狀況下,咱們的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);
測試結果:
在實際的業務環境中,咱們的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);
測試結果:
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 });
或者使用自定義解析器,自定義解析器在下面講到。
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 });
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 }
空值替換容許咱們將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);
測試結果:
條件映射只當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 });
做者:齊飛