1. 程式人生 > >【AutoMapper官方文件】DTO與Domin Model相互轉換(上)

【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(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。

後記

  貪多嚼不爛,關於AutoMapper的使用先整理這些,後面會陸續更新,還請關注。

  AutoMapper在配置型別對映最注意的一點是,型別中的名稱一定要按照PascalCase命名規則(Projection和Ignore除外)。

  如果你覺得本篇文章對你有所幫助,請點選右下部“推薦”,^_^

  參考資料:

相關推薦

AutoMapper官方DTODomin Model相互轉換

寫在前面   AutoMapper目錄:   本篇目錄:   上一篇《【道德經】漫談實體、物件、DTO及AutoMapper的使用 》,因為內容寫的有點跑偏,關於AutoMapper的使用最後只是簡單寫了下,很明顯這種簡單的使用方式不能滿足專案中複雜的需要,網上找了下AutoMapper相關文件

AutoMapper官方DTODomin Model相互轉換

寫在前面   AutoMapper目錄:   本篇目錄:   隨著AutoMapper的學習深入,發現AutoMapper在物件轉換方面(Object-Object Mapping)還蠻強大的,當時使用AutoMapper的場景是DTO與Domin Model相互轉換,所以文章的標題就是這個(標

AutoMapper官方DTODomin 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》 譯者序言: 由

小白學PyTorch21 Keras的API詳解卷積、啟用、初始化、正則

【新聞】:機器學習煉丹術的粉絲的人工智慧交流群已經建立,目前有目標檢測、醫學影象、時間序列等多個目標為技術學習的分群和水群嘮嗑答疑解惑的總群,歡迎大家加煉丹兄為好友,加入煉丹協會。微信:cyx645016617. 參考目錄: [TOC] 我們對Keras應該已經有了一個直觀、巨集觀的認識了。現在,我們來系

unicodeGB2312的相互轉換js

上回說到,我們用C語言輸出了一張GB2312的全部字元表……同時也說,有了這個,我們就能實現使用js進行unicode和GB2312之間的轉碼了……再加上前回(其實是幾年之前)說到,用js沒有內建函式實現這兩者的轉碼,如果用到,一般都是藉助於vbs……這使得我的BF直譯器(

xmlbean間相互轉換補充

今天x被stream對xmlnode的屬性(attribute)解析的問題一直困擾著,查詢了很久都告知我要手寫一個Converter,那豈不意味著我每解析一個xml檔案,就得寫一次Converter,那樣太腦殘了,最後搜尋到其實可以用註解解決這個問題 XStream常用註解

AutoMapper官方(二)升級指南

初始化 您現在必須使用Mapper.Initialize或new MapperConfiguration()來初始化AutoMapper。如果您希望保持靜態使用,請使用Mapper.Initialize。 如果你有很多的Mapper.CreateMap呼叫,把它們移動到一個Profile,或者Mapper

pythonnumpy庫陣列拼接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) 管理使用者註