書接上文。在上一篇文章中我們討論了使用AutoMapper實作類型間1-1映射的兩種方式-Convention和Configuration,知道如何進行簡單的OO Mapping。在這個系列的最後一篇文章我想基於我們的需求討論一些中級別的話題,包括:如何實現類型體型之間的映射,以及如何為兩個類型實現多個映射規則。
【四】將一個類型映射為類型體系
先回顧一下我們的Dto和Model。我們有BookDto,我們有Author,每個Author有自己的ContactInfo。現在提一個問題:如何從BookDto得到第一個作者的Author物件?答案即簡單,又不簡單。
最簡單的做法是,使用前面提到的CountructUsing,指定BookDto到Author的全部欄位及子類型欄位的對應:
C#程式碼
var map
map.ConstructUsing(s => new Author Name = s.First AuthorName, Description = s.FirstAuthorDescription, ContactInfo .FirstAuthorBlog, Email = s.FirstAuthorEmail, Twitter = s.FirstAuthorTwitter }
這樣的做法可以工作,但很不經濟。因為我們是在從頭開始做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為實現字段層級的特定映射邏輯而定義的類型,它的定義如下:
C#代碼
public interface IValueResolver source);
}
而在實際的應用中我們傾向於使用它的泛型子類別-ValueResolver,並實現它的抽象方法:C#程式碼
protected abstract TD ,TDestination為目標欄位的類型。
C#代碼
var map = Mapper.CreateMap
.ForMember(d => d.Description,opt .ForMember(d => d.Description,1opt .ForMember(d => d.Description,Fopt .ForMember(d
.ForMember(d => d.ContactInfo,
opt => opt.ResolveUsing
} 搞定了。
類似的,我們現在也可以實現BookDto -> Book了吧?透過重複使用BookDto -> Author以及BookDto -> Publisher。
【五】為兩個類型實現多套映射規則
我們的問題是:對於類型A和B,需要定義2個不同的A -> B,並讓它們可以同時使用。事實上目前的AutoMapper並沒有提供現成的方式做到這一點。當然我們可以採用「曲線救國」的方法-為first author和second author分別定義Author的兩個子類,比如說FirstAuthor和SecondAuthor,然後分別實作BookDto -> FirstAuthor和BookDto -> SecondAuthor映射。但是這種方法也不太經濟。假如還有第三作者甚至第四作者呢?為每一個作者都定義一個Author的子類別嗎?
另一方面,我們不妨假設一下,如果AutoMapper提供了這樣的功能,那會是什麼樣子呢? CreateMap方法與Map方法應該這樣定義:C#程式碼
CreateMap
而我們在使用的時候,就可以:
C#代碼 var firstAuthorMap = Mapper.CreateMap// Define BookDto -> first Author rule
var secondAuthorMap = Mapper.CreateMap
var firstAuthor = Mapper.Map
var secondAuthor = Mapper.Map
MappingEngine是AutoMapper的映射執行引擎,事實上在Mapper中有預設的MappingEngine,我們在呼叫Mapper.CreateMap的時候,是往與這個預設的MappingEngine對應的Configuration中寫規則,在呼叫Mapper.Map取得物件的時候則是使用預設的MappingEngine執行其對應Configuration中的規則。
簡而言之一個MappingEngine就是一個AutoMapper的“虛擬機”,如果我們同時啟動多個“虛擬機”,並且將針對同一對類型的不同映射規則放到不同的“虛擬機”上,就可以讓它們各自相安無事的運作起來,使用的時候要用哪個規則就問相應的「虛擬機器」去要好了。
說做就做。首先我們定義一個MappingEngineProvider類,用它來取得不同的MappingEngine:
C#代碼
public class MappingEngineProvider
public MappingEngine Get()
{
{ _engine;
}
}
我們將不同類型的對應規則抽象化為介面IMapping: void AddTo(Configuration config); }
接著在MappingEngineProvider的建構子裡將需要的規則放到對應的MappingEngine中:
public enum Engine
{ Basic = 0, First,
}用於放置所有基本的映射規則,First用於放置所有Dto -> FirstXXX的規則, Second則用來放置所有Dto -> SecondXXX的規則。
我們也定義了一個放置所有映射規則的字典_rule,將規則分門別類別放到不同的Engine中。剩下的事情就是往字典_rule裡填入我們的mapping了。例如我們把BookDtoToFirstAuthorMapping放到First engine裡並把BookDtoToSecondAuthorMapping放到Second
new Dictionary
{
} {
Engine.Sec {
new BookDtoToSecondAuthorMapping(),
},
};
};
當然為了方便使用我們可以事先實例化好不同的MappingEngineProvider物件:C#程式碼
public static SimpleMap pingEngineProvider Second = new MappingEngineProvider(Engine.Second) ;
現在我們就可以在映射BookDto -> Book的時候同時使用這2個Engine來得到2個Author並把它們組裝到字段Book.Authors裡面了:
DefaultMapping
: new List
}
}
最後,但還記得我們在本節開始的時候提到的美好意願嗎?既然AutoMapper沒有幫我們實現,就讓我們自己實現:
C#代碼
public class MyMapper
{ ines = new Dictionary
{
{Engine.Basic, MappingEngineProvider.Basic.Get()}, 地},
{Engine.Second, MappingEngineProvider.Second.Get()},
};
public static TTarget Map
{
return Engines[engine].Map
}
}
一切又回來了,我們可以這樣做:C#代碼
var firstAuthor var secondAuthor = MyMapper. Map
C#代碼
發現在家裡要上傳文件到Github真是奇慢無比,所有我決定先把自己的程式碼打包上傳,歡迎大家參考使用。