寫在前面
AutoMapper目錄:
- 【AutoMapper官方文檔】DTO與Domin Model相互轉換(上)
- 【AutoMapper官方文檔】DTO與Domin Model相互轉換(中)
- 【AutoMapper官方文檔】DTO與Domin Model相互轉換(下)
- 未完待續。。。
本篇目錄:
上一篇《【道德經】漫談實體、對象、DTO及AutoMapper的使用 》,因為內容寫的有點跑偏,關于AutoMapper的使用最后只是簡單寫了下,很明顯這種簡單的使用方式不能滿足項目中復雜的需要,網上找了下AutoMapper相關文檔,但差不多都是像我一樣簡單的概述下,看來懶的不只有我一個,哈哈。在AutoMapper 官方文檔中找到其使用的詳細說明,天書一樣的英文,然后就找相關中文文檔,最后還是沒找到,這邊沒辦法,只能自己動手,豐衣足食了。英語牛逼的可以直接略過,查看英文文檔,本篇也不算是翻譯,因為本人英語實在拿不出手,只是按照示例加上自己的一些理解,做個學習筆記,不對的地方還請指正。
注:雖然上一篇寫跑偏了,但是本人真的很喜歡道德經,除了為人處世,在軟件設計這方面其實也有體現,也希望可以運用到這上面,如果你和我有一樣的想法,請在點擊公告欄中的QQ鏈接,知音難覓啊!
Flattening-復雜到簡單
Flattening 翻譯為壓扁、拉平、扁平化的意思,可以理解為使原有復雜的結構變得簡化,我們先看下領域模型和DTO代碼:
1 public class Order 2 { 3 private readonly IList<OrderLineItem> _orderLineItems = new List<OrderLineItem>(); 4 public Customer Customer { get; set; } 5 public OrderLineItem[] GetOrderLineItems() 6 { 7 return _orderLineItems.ToArray(); 8 } 9 public void AddOrderLineItem(Product product, int quantity) 10 { 11 _orderLineItems.Add(new OrderLineItem(product, quantity)); 12 } 13 public decimal GetTotal() 14 { 15 return _orderLineItems.Sum(li => li.GetTotal()); 16 } 17 } 18 19 public class Product 20 { 21 public decimal Price { get; set; } 22 public string Name { get; set; } 23 } 24 25 public class OrderLineItem 26 { 27 public OrderLineItem(Product product, int quantity) 28 { 29 Product = product; 30 Quantity = quantity; 31 } 32 public Product Product { get; private set; } 33 public int Quantity { get; private set; } 34 public decimal GetTotal() 35 { 36 return Quantity * Product.Price; 37 } 38 } 39 40 public class Customer 41 { 42 public string Name { get; set; } 43 } 44 45 public class OrderDto 46 { 47 public string CustomerName { get; set; } 48 public decimal Total { get; set; } 49 }
可以看到領域模型 Order 是很復雜的,但是對于業務場景中的OrderDto卻很簡單,只有 CustomerName和Total兩個屬性,AutoMapper配置代碼:
1 public void Example() 2 { 3 var customer = new Customer 4 { 5 Name = "George Costanza" 6 }; 7 var order = new Order 8 { 9 Customer = customer 10 }; 11 var bosco = new Product 12 { 13 Name = "Bosco", 14 Price = 4.99m 15 }; 16 order.AddOrderLineItem(bosco, 15); 17 // 配置 AutoMapper 18 Mapper.CreateMap<Order, OrderDto>(); 19 // 執行 mapping 20 OrderDto dto = Mapper.Map<Order, OrderDto>(order); 21 Console.WriteLine("CustomerName:" + dto.CustomerName); 22 Console.WriteLine("Total:" + dto.Total); 23 }
轉換效果:
可以看到配置相當的簡單,只要設置下Order和OrderDto之間的類型映射就可以了,我們看OrderDto中的CustomerName和Total屬性在領域模型Order中并沒有與之相對性,沒什么可以轉換呢,感覺好神奇的樣子,其實仔細發現這些屬性的命名都有一定的規則,AutoMapper在做解析的時候會按照PascalCase(帕斯卡命名法),就是一種變量命名法,除了PascalCase還有Hungarian(匈牙利命名法)和camelCase(駱駝命名法),PascalCase就是指混合使用大小寫字母來構成變量和函數的名字,首字母要大寫,camelCase首字母小寫,我們C#命名中,一般使用的是camelCase和PascalCase,比較高級的是PascalCase。
但是為什么AutoMapper會解析Total呢?因為在領域模型Order中有個GetTotal()方法,AutoMapper會解析“Get”之后的單詞,所以會與Total相對應,如果你把OrderDto的屬性“Total”改為“Totals”,就會發現得到的“Totals”為0。理解了AutoMapper的解析方式,我們就要注意在編寫變量、屬性或是方法名稱的時候一定要規范,這也是一種好的習慣。
Projection-簡單到復雜
Projection 翻譯為投影,Flattening是由復雜結構簡化,Projection正好相反,投影可以理解為由原始結構千變萬化,我們看下兩種轉換結構:
1 public class CalendarEvent 2 { 3 public DateTime EventDate { 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 Title { get; set; } 13 }
CalendarEvent是原始結構,CalendarEventForm是我們需要轉換后的結構,可以看到CalendarEventForm要比CalendarEvent結構復雜些,看下AutoMapper配置轉換代碼:
1 public void Example() 2 { 3 var calendarEvent = new CalendarEvent 4 { 5 EventDate = new DateTime(2008, 12, 15, 20, 30, 0), 6 Title = "Company Holiday Party" 7 }; 8 9 // 配置 AutoMapper 10 Mapper.CreateMap<CalendarEvent, CalendarEventForm>() 11 .ForMember(dest => dest.EventDate, opt => opt.MapFrom(src => src.EventDate.Date))//定義映射規則 12 .ForMember(dest => dest.EventHour, opt => opt.MapFrom(src => src.EventDate.Hour))//定義映射規則 13 .ForMember(dest => dest.EventMinute, opt => opt.MapFrom(src => src.EventDate.Minute));//定義映射規則 14 15 // 執行 mapping 16 CalendarEventForm form = Mapper.Map<CalendarEvent, CalendarEventForm>(calendarEvent); 17 18 Console.WriteLine("EventDate:"+form.EventDate); 19 Console.WriteLine("EventHour:" + form.EventHour); 20 Console.WriteLine("EventMinute:" + form.EventMinute); 21 Console.WriteLine("Title:" + form.Title); 22 }
和Flattening不同的是,我們除了定義類型映射,還要自定義映射規則,src.EventDate.Date指向dest.EventDate,src.EventDate.Minute指向dest.EventMinute,src.EventDate.Hour指向dest.EventHour,當然我們還可以在MapFrom方法中做一些復雜的映射關系操作,MapFrom接受一個lambda表達式作為參數,可以是任何的Func表達式。Projection適用于由簡單到復雜的結構映射,一般體現在業務場景很復雜的情況下。
【更正:Projection也不一定適用在由簡單到復雜的場景,應該說使用Projection就是把AutoMapper的映射配置交給用戶來操作】
Configuration Validation-配置驗證
我們在使用Flattening的前提是我們需要轉換的結構命名是沒有錯誤的,但是如果我們沒有使用PascalCase命名法,或者說我們命名是錯誤的,該怎么辦呢?比如下面代碼:
1 public class Source 2 { 3 public int SomeValue { get; set; } 4 } 5 6 public class Destination 7 { 8 public int SomeValuefff { get; set; } 9 }
可以看到Source和Destination中的字段并不相對應,我們測試下AutoMapper映射:
AssertConfigurationIsValid方法是驗證結構映射的,如果配置不正確,會報“AutoMapperConfigurationException”異常錯誤,如何解決這個問題?你可能會說,就不能改下SomeValuefff的名稱嗎?這種方法可以,但是如果業務場景中必須要使用怎么辦呢,看了上面Projection的映射配置,你可能想到解決方法了,如下:
1 Mapper.CreateMap<Source, Destination>() 2 .ForMember(dest => dest.SomeValuefff, opt => opt.MapFrom(src => src.SomeValue));
名稱不對,我們可以自定義映射規則,雖然這種方式可以,但是如果業務場景中SomeValuefff并不需要,那我們改怎么辦?既然有問題,就有解決之道,AutoMapper提供了Ignore方法,忽略不需要映射的數據結構,我們這樣配置就可以了:
1 Mapper.CreateMap<Source, Destination>() 2 .ForMember(dest => dest.SomeValuefff, opt => opt.Ignore());
Lists and Array-集合和數組
有時候我們除了類型映射之外,還需要對集合類型進行映射,先看個示例:
1 public void Example() 2 { 3 var sources = new[] 4 { 5 new Source {Value = 5}, 6 new Source {Value = 6}, 7 new Source {Value = 7} 8 }; 9 //配置AutoMapper 10 Mapper.Initialize(cfg => 11 { 12 cfg.CreateMap<Source, Destination>(); 13 }); 14 //配置和執行映射 15 IEnumerable<Destination> ienumerableDest = Mapper.Map<Source[], IEnumerable<Destination>>(sources); 16 ICollection<Destination> icollectionDest = Mapper.Map<Source[], ICollection<Destination>>(sources); 17 IList<Destination> ilistDest = Mapper.Map<Source[], IList<Destination>>(sources); 18 List<Destination> listDest = Mapper.Map<Source[], List<Destination>>(sources); 19 20 Console.WriteLine("ienumerableDest.Count:" + ienumerableDest.Count()); 21 Console.WriteLine("icollectionDest.Count:" + icollectionDest.Count()); 22 Console.WriteLine("ilistDest.Count:" + ilistDest.Count()); 23 Console.WriteLine("listDest.Count:" + listDest.Count()); 24 }
轉換結果:
Source和Destination結構類型只有一個Value屬性,可以看到對集合類型映射也很簡單,只需要執行Mapper.Map泛型方法,指定需要轉換的集合類型即可,AutoMapper所支持的集合類型包括:
- IEnumerable
- IEnumerable<T>
- ICollection
- ICollection<T>
- IList
- IList<T>
- List<T>
- Arrays
我們在使用Mapper.Map執行類型映射的時候,如果來源類型支持上述集合類型,我們可以把來源類型省略掉,因為AutoMapper會自動判斷傳入對象sources的類型,如下:
1 IEnumerable<Destination> ienumerableDest = Mapper.Map<IEnumerable<Destination>>(sources); 2 ICollection<Destination> icollectionDest = Mapper.Map<ICollection<Destination>>(sources); 3 IList<Destination> ilistDest = Mapper.Map<IList<Destination>>(sources); 4 List<Destination> listDest = Mapper.Map<List<Destination>>(sources);
還有一種情況是,在使用集合類型類型的時候,類型之間存在繼承關系,例如下面我們需要轉換的類型:
1 public class ParentSource 2 { 3 public int Value1 { get; set; } 4 } 5 public class ChildSource : ParentSource 6 { 7 public int Value2 { get; set; } 8 } 9 public class ParentDestination 10 { 11 public int Value1 { get; set; } 12 } 13 public class ChildDestination : ParentDestination 14 { 15 public int Value2 { get; set; } 16 }
ChildSource繼承ParentSource,ChildDestination繼承ParentDestination,看下AutoMapper配置轉換代碼:
1 public void Example() 2 { 3 var sources = new[] 4 { 5 new ParentSource(), 6 new ChildSource(), 7 new ParentSource() 8 }; 9 //配置AutoMapper 10 Mapper.Initialize(cfg => 11 { 12 cfg.CreateMap<ParentSource, ParentDestination>() 13 .Include<ChildSource, ChildDestination>(); 14 cfg.CreateMap<ChildSource, ChildDestination>(); 15 }); 16 //配置和執行映射 17 var destinations = Mapper.Map<ParentSource[], ParentDestination[]>(sources); 18 Console.WriteLine("destinations[0] Type:" + destinations[0].GetType().ToString()); 19 Console.WriteLine("destinations[1] Type:" + destinations[1].GetType().ToString()); 20 Console.WriteLine("destinations[2] Type:" + destinations[2].GetType().ToString()); 21 }
轉換結果:
注意在Initialize初始化CreateMap進行類型映射配置的時候有個Include泛型方法,簽名為:“Include this configuration in derived types' maps”,大致意思為包含派生類型中配置,ChildSource是ParentSource的派生類,ChildDestination是ParentDestination的派生類,cfg.CreateMap<ParentSource, ParentDestination>().Include<ChildSource, ChildDestination>(); 這段代碼只是說明ParentSource和ChildSource之間存在的關系,我們如果把這段代碼注釋掉,就會報上面“AutoMapperMappingException”類型指定不正確的異常錯誤,如果我們把下面這段代碼:“cfg.CreateMap<ChildSource, ChildDestination>();”注釋掉,轉換結果為:
雖然沒有報“AutoMapperMappingException”異常,但是可以看出AutoMapper并沒有從ChildSource類型映射到ChildDestination類型,而是自動映射到基類型,上面那段映射代碼只是說明派生類和基類之間存在的關系,如果派生類需要映射的話,是需要添加派生類的映射的。
Nested mappings-嵌套映射
我們上面說的集中映射方式都是簡單類型映射,就是類型中并不包含其他類型的映射,如何在嵌套類型中執行映射?請看下面示例:
1 public class OuterSource 2 { 3 public int Value { get; set; } 4 public InnerSource Inner { get; set; } 5 } 6 public class InnerSource 7 { 8 public int OtherValue { get; set; } 9 } 10 public class OuterDest 11 { 12 public int Value { get; set; } 13 public InnerDest Inner { get; set; } 14 } 15 public class InnerDest 16 { 17 public int OtherValue { get; set; } 18 }
OuterSource和OuterDest類型是我們需要映射的類型,可以看到OuterSource類型中嵌套了InnerSource類型,OuterDest類型中嵌套了InnerDest類型,AutoMapper類型映射配置代碼:
1 public void Example() 2 { 3 var source = new OuterSource 4 { 5 Value = 5, 6 Inner = new InnerSource { OtherValue = 15 } 7 }; 8 //配置AutoMapper 9 Mapper.CreateMap<OuterSource, OuterDest>(); 10 Mapper.CreateMap<InnerSource, InnerDest>(); 11 //驗證類型映射是否正確 12 Mapper.AssertConfigurationIsValid(); 13 //執行映射 14 var dest = Mapper.Map<OuterSource, OuterDest>(source); 15 Console.WriteLine("dest.Value:" + dest.Value); 16 Console.WriteLine("dest.Inner is null:" + (dest.Inner == null ? "true" : "false")); 17 Console.WriteLine("dest.Inner.OtherValue:" + dest.Inner.OtherValue); 18 }
轉換結果:
上面代碼中可以看出,對于嵌套映射,我們不需要配置什么,只要指定下類型映射關系和嵌套類型映射關系就可以了,也就是這段代碼:“Mapper.CreateMap<InnerSource, InnerDest>();” 其實我們在驗證類型映射的時候加上Mapper.AssertConfigurationIsValid(); 這段代碼看是不是拋出“AutoMapperMappingException”異常來判斷類型映射是否正確,因為AssertConfigurationIsValid方法沒有返回值,只能在catch中捕獲了,個人感覺AutoMapper可以提供個bool類型的返回值,驗證成功則返回true。
后記
示例代碼下載:http://pan.baidu.com/s/10A7WM
貪多嚼不爛,關于AutoMapper的使用先整理這些,后面會陸續更新,還請關注。
AutoMapper在配置類型映射最注意的一點是,類型中的名稱一定要按照PascalCase命名規則(Projection和Ignore除外)。
如果你覺得本篇文章對你有所幫助,請點擊右下部“推薦”,^_^
參考資料:
文章列表