天天看點

使用AutoMapper實作Dto和Model的自由轉換(下)

書接上文。在上一篇文章中我們讨論了使用automapper實作類型間1-1映射的兩種方式——convention和configuration,知道了如何進行簡單的oo mapping。在這個系列的最後一篇文章我想基于我們的需求讨論一些中級别的話題,包括:如何實作類型體型之間的映射,以及如何為兩個類型實作多個映射規則。

【四】将一個類型映射為類型體系

先回顧一下我們的dto和model。我們有bookdto,我們有author,每個author有自己的contactinfo。現在提一個問題:如何從bookdto得到第一個作者的author對象呢?答案即簡單,又不簡單。

最簡單的做法是,使用前面提到的countructusing,指定bookdto到author的全部字段及子類型字段的映射:

這樣的做法可以工作,但很不經濟。因為我們是在從頭做bookdto到author的映射,而從bookdto到contactinfo的映射是我們之前已經實作過的,實在沒有必要重複再寫一遍。設想一下,如果有一個别的什麼reader類型裡面也包含有contactinfo,在做bookdto到reader映射的時候,我們是不是再寫一遍這個bookdto -> contactinfo邏輯呢?再設想一下如果我們在實作bookdto到book的映射的時候,是不是又需要把bookdto到author的映射規則再重複寫一遍呢?

是以我認為對于這種類型體系間的映射,比較理想的做法是為每個具體類型指定簡單的映射,而後在映射複雜類型的時候再複用簡單類型的映射。用簡單點的語言描述:

我們有a,b,c,d四個類型,其中b = [c, d]。已知a -> c, a -> d, 求a -> b。

我的解法是使用automapper提供的——ivalueresolver。ivalueresolver是automapper為實作字段級别的特定映射邏輯而定義的類型,它的定義如下:

而在實際的應用中我們往往會使用它的泛型子類——valueresolver,并實作它的抽象方法:

其中tsource為源類型,tdestination為目标字段的類型。

回到我們的例子,我們現在可以這樣來映射bookdto -> author:

在firstauthorcontactinforesolver中我們實作valueresolver并複用bookdto -> contactinfo的邏輯:

一切就搞定了。

類似的,我們現在也可以實作bookdto -> book了吧?通過複用bookdto -> author以及bookdto -> publisher。

真的可以嗎?好像還有問題。是的,我們會發現需要從bookdto映射到兩個不同的author,它們的字段映射規則是不同的。怎麼辦?趕緊進入我們的最後一個議題。

【五】為兩個類型實作多套映射規則

我們的問題是:對于類型a和b,需要定義2個不同的a -> b,并讓它們可以同時使用。事實上目前的automapper并沒有提供現成的方式做到這一點。

當然我們可以采用“曲線救國”的辦法——為first author和second author分别定義author的兩個子類,比如說firstauthor和secondauthor,然後分别實作bookdto -> firstauthor和bookdto -> secondauthor映射。但是這種方法也不太經濟。假如還有第三作者甚至第四作者呢?為每一個作者都定義一個author的子類嗎?

另一方面,我們不妨假設一下,如果automapper提供了這樣的功能,那會是什麼樣子呢?createmap方法和map方法應該這樣定義:

其中有一個額外的參數tag用于辨別該映射的标簽。

而我們在使用的時候,就可以:

遺憾的是,這一切都是假如。但是沒有關系,雖然automapper關上了這扇門,卻為我們留着另一扇門——mappingengine。

mappingengine是automapper的映射執行引擎,事實上在mapper中有預設的mappingengine,我們在調用mapper.createmap的時候,是往與這個預設的mappingengine對應的configuration中寫規則,在調用mapper.map擷取對象的時候則是使用預設的mappingengine執行其對應configuration中的規則。

簡而言之一個mappingengine就是一個automapper的“虛拟機”,如果我們同時啟動多個“虛拟機”,并且将針對同一對類型的不同映射規則放到不同的“虛拟機”上,就可以讓它們各自相安無事的運作起來,使用的時候要用哪個規則就問相應的“虛拟機”去要好了。

說做就做。首先我們定義一個mappingengineprovider類,用它來擷取不同的mappingengine:

我們将不同類型的映射規則抽象為接口imapping:

然後在mappingengineprovider的構造函數裡将需要的規則放到對應的mappingengine中:

注意到這裡我們用了一個枚舉類型engine用于辨別可能的mappingengine:

我們用到了3個engine,basic用于放置所有基本的映射規則,first用于放置所有dto -> firstxxx的規則,second則用于放置所有dto -> secondxxx的規則。

我們還定義了一個放置所有映射規則的字典_rule,将規則分門别類放到不同的engine中。

剩下的事情就是往字典_rule裡填充我們的mapping了。比如說我們把bookdtotofirstauthormapping放到first engine裡并把bookdtotosecondauthormapping放到second engine裡:

當然為了友善使用我們可以事先執行個體化好不同的mappingengineprovider對象:

現在我們就可以在映射bookdto -> book的時候同時使用這2個engine來得到2個author并把它們組裝到字段book.authors裡面了:

最後,還記得我們在本節開始的時候提到的美好願望嗎?既然automapper沒有幫我們實作,就讓我們自己來實作吧:

一切又都回來了,我們可以這樣:

也可以這樣了:

後記: 發現在家裡要上傳檔案到github真是奇慢無比,所有我決定先把自己的代碼打包上傳,歡迎大家參考使用。(代碼太久遠了,我沒下載下傳,正文裡基本都囊括了)