【AutoMapper官方文件】DTO與Domin Model相互轉換(上)
寫在前面
AutoMapper目錄:
本篇目錄:
上一篇《【道德經】漫談實體、物件、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(newOrderLineItem(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。
後記
貪多嚼不爛,關於AutoMapper的使用先整理這些,後面會陸續更新,還請關注。
AutoMapper在配置型別對映最注意的一點是,型別中的名稱一定要按照PascalCase命名規則(Projection和Ignore除外)。
如果你覺得本篇文章對你有所幫助,請點選右下部“推薦”,^_^
參考資料:
相關推薦
【AutoMapper官方文件】DTO與Domin Model相互轉換(上)
寫在前面 AutoMapper目錄: 本篇目錄: 上一篇《【道德經】漫談實體、物件、DTO及AutoMapper的使用 》,因為內容寫的有點跑偏,關於AutoMapper的使用最後只是簡單寫了下,很明顯這種簡單的使用方式不能滿足專案中複雜的需要,網上找了下AutoMapper相關文件
【AutoMapper官方文件】DTO與Domin Model相互轉換(中)
寫在前面 AutoMapper目錄: 本篇目錄: 隨著AutoMapper的學習深入,發現AutoMapper在物件轉換方面(Object-Object Mapping)還蠻強大的,當時使用AutoMapper的場景是DTO與Domin Model相互轉換,所以文章的標題就是這個(標
【AutoMapper官方文件】DTO與Domin Model相互轉換(下)
寫在前面 AutoMapper目錄: 本篇目錄: 關於AutoMapper寫到這基本的東西都差不多了,上一篇定義為靈活配置篇,本篇可以定義為擴充套件應用篇,加一些補充,關於AutoMapper的專案應用,網上找了幾篇英文文章,雖然看不懂,但是程式碼是相通的,感覺很不錯,主要是Enti
【android官方文件】android AIDL
概述 AIDL(安卓介面解釋語言)和其他的IDLs類似。可以定義程式介面讓客戶端和service進行跨程序的通訊(IPC)。在android中,一個程序通常不能訪問另一個程序的記憶體。所以,他們的物件需要被分解成更原始的單位,直到系統可以理解,並且集結這些物件穿
【Android官方文件】翻譯Android官方文件-Activities(一)
Activity是可以給使用者提供互動操作的程式元件,例如打電話,拍照,傳送郵件,抑或者是顯示地圖。通常視窗會填滿螢幕,但是也可以做到比螢幕小或者是懸浮在視窗頂部。 App通常由多個Activities組成,它們之間支援相互跳轉。一般情況下,每個Activit
【pytest官方文件】解讀fixtures - 1.什麼是fixtures
在深入瞭解fixture之前,讓我們先看看什麼是`測試`。 ### 一、測試的構成 其實說白了,測試就是在特定的環境、特定的場景下、執行特定的行為,然後確認結果與期望的是否一致。 就拿最常見的登入來說,完成一次正常的登入場景,需要可用的測試環境,可以正常登入的賬號和密碼。 然後,用這個賬號密碼進行登入操
【pytest官方文件】解讀fixtures - 2. fixtures的呼叫方式
既然fixtures是給執行測試做準備工作的,那麼pytest如何知道哪些測試函式 或者 fixtures要用到哪一個fixtures呢? 說白了,就是fixtures的呼叫。 ### 一、測試函式宣告傳參請求fixture 測試函式通過將fixture宣告為引數來請求fixture。 ``` def te
【pytest官方文件】解讀fixtures - 8. yield和addfinalizer的區別(填坑)
在[上一章](https://www.cnblogs.com/pingguo-softwaretesting/p/14479170.html)中,文末留下了一個坑待填補,疑問是這樣的: 目前從官方文件中看到的是 ``` We have to be careful though, because pytest
Pulsar官方文件翻譯-入門必看-概念和架構-(一)概覽(Pulsar Overview)
官網原文標題《Concepts and Architecture--Pulsar Overview》 翻譯時間:2018-09-28 譯者:本文介紹了Pulsar的起源和現狀,以及主要特性。 後續閱讀:《Messaging Concepts》 譯者序言: 由
【小白學PyTorch】21 Keras的API詳解(上)卷積、啟用、初始化、正則
【新聞】:機器學習煉丹術的粉絲的人工智慧交流群已經建立,目前有目標檢測、醫學影象、時間序列等多個目標為技術學習的分群和水群嘮嗑答疑解惑的總群,歡迎大家加煉丹兄為好友,加入煉丹協會。微信:cyx645016617. 參考目錄: [TOC] 我們對Keras應該已經有了一個直觀、巨集觀的認識了。現在,我們來系
unicode與GB2312的相互轉換(js)
上回說到,我們用C語言輸出了一張GB2312的全部字元表……同時也說,有了這個,我們就能實現使用js進行unicode和GB2312之間的轉碼了……再加上前回(其實是幾年之前)說到,用js沒有內建函式實現這兩者的轉碼,如果用到,一般都是藉助於vbs……這使得我的BF直譯器(
xml與bean間相互轉換(補充)
今天x被stream對xmlnode的屬性(attribute)解析的問題一直困擾著,查詢了很久都告知我要手寫一個Converter,那豈不意味著我每解析一個xml檔案,就得寫一次Converter,那樣太腦殘了,最後搜尋到其實可以用註解解決這個問題 XStream常用註解
AutoMapper官方文件(二)【升級指南】
初始化 您現在必須使用Mapper.Initialize或new MapperConfiguration()來初始化AutoMapper。如果您希望保持靜態使用,請使用Mapper.Initialize。 如果你有很多的Mapper.CreateMap呼叫,把它們移動到一個Profile,或者Mapper
【python】numpy庫陣列拼接np.concatenate官方文件詳解與例項
在實踐過程中,會經常遇到陣列拼接的問題,基於numpy庫concatenate是一個非常好用的陣列操作函式。 1、concatenate((a1, a2, …), axis=0)官方文件詳解 concatenate(...) concatenate(
【pySerial3.4官方文件】6、示例
示例 Miniterm Miniterm現在可用作模組而不是示例。有關詳細資訊,請參閱serial.tools.miniterm。 miniterm.py miniterm計劃。 setup-miniterm-py2exe.py 這是Windows的py2exe安
【pySerial3.4官方文件】4、工具
工具 serial.tools.list_ports 可以執行此模組以獲取埠列表()。它還包含以下功能。python -m serial.tools.list_ports serial.tools.list_ports.comports(include_l
【pySerial3.4官方文件】3、pySerial API
pySerial API 類 本地埠 類serial.Serial __init__(port = None,baudrate = 9600,bytesize = EIGHTBITS,parity = PARITY_NONE,stopbits = STOPBITS_ONE
【pySerial3.4官方文件】2、簡介
簡介 開啟串列埠 開啟“9600,8,N,1”的埠,沒有超時: >>> import serial >>> ser = serial.Serial('/dev/ttyUSB0') # open serial port >>> pri
【Gradle官方文件翻譯】起步2:建立構建掃描
構建掃描是對構建的可分享的專門記錄,可以看到“構建中發生了那些行為以及為什麼會發生這種行為”。通過在專案中使用構建掃描外掛,開發者可以免費地在https://scans.gradle.com/上釋出構建掃描。 將要建立的 本文會展示如何在不對任何構建指令碼進行
【cocos2d-js官方文件】十七、事件分發機制
簡介 遊戲開發中一個很重要的功能就是互動,如果沒有與使用者的互動,那麼遊戲將變成動畫,而處理使用者互動就需要使用事件監聽器了。 總概: 事件監聽器(cc.EventListener) 封裝使用者的事件處理邏輯事件管理器(cc.eventManager) 管理使用者註