註:本系列文章的代碼可以在這裡下載。
在上一篇文章中我們構造出了完整的應用情境,包括我們的Model、Dto以及它們之間的轉換規則。下面就可以捲起袖子,開始我們的AutoMapper之旅了。
【二】以Convention方式實現零配置的對象映射
我們的AddressDto和Address結構完全一致,且欄位名也完全相同。對於這樣的類型轉換,AutoMapper為我們提供了Convention,正如它的官網上所說的:
引用
AutoMapper uses a convention-based matching algorithm to match up source to destination values.
我們要做的只是將要映射的兩個類型告訴AutoMapper(調用Mapper類的Static方法CreateMap並傳入要映射的類型):
C#代碼
Mapper.CreateMap<AddressDto, Address>();
然後就可以交給AutoMapper幫我們搞定一切了:
C#代碼
AddressDto dto = new AddressDto
{
Country = "China",
City = "Beijing",
Street = "Dongzhimen Street",
PostCode = "100001"
};
Address address = Mapper.Map<AddressDto,Address>(Dto);
address.Country.ShouldEqual("China");
address.City.ShouldEqual("Beijing");
address.Street.ShouldEqual("Dongzhimen Street");
address.PostCode.ShouldEqual("100001");
如果AddressDto中有值為空白的屬性,AutoMapper在映射的時候會把Address中的相應屬性也置為空白:
C#代碼
Address address = Mapper.Map<AddressDto,Address>(new AddressDto
{
Country = "China"
});
address.City.ShouldBeNull();
address.Street.ShouldBeNull();
address.PostCode.ShouldBeNull();
甚至如果傳入一個空的AddressDto,AutoMapper也會幫我們得到一個空的Address對象。
C#代碼
Address address = Mapper.Map<AddressDto,Address>(null);
address.ShouldBeNull();
千萬不要把這種Convention的映射方式當成“玩具”,它在映射具有相同欄位名的複雜類型的時候還是具有相當大的威力的。
例如,考慮我們的BookStoreDto到BookStore的映射,兩者的欄位名稱完全相同,只是欄位的類型不一致。如果我們定義好了BookDto到Book的映射規則,再加上上述Convention方式的AddressDto到Address的映射,就可以用“零配置”實現BookStoreDto到BookStore的映射了:
C#代碼
IMappingExpression<BookDto, Book> expression = Mapper.CreateMap<BookDto,Book>();
// Define mapping rules from BookDto to Book here
Mapper.CreateMap<AddressDto, Address>();
Mapper.CreateMap<BookStoreDto, BookStore>();
然後我們就可以直接轉換BookStoreDto了:
C#代碼
BookStoreDto dto = new BookStoreDto
{
Name = "My Store",
Address = new AddressDto
{
City = "Beijing"
},
Books = new List<BookDto>
{
new BookDto {Title = "RESTful Web Service"},
new BookDto {Title = "Ruby for Rails"},
}
};
BookStore bookStore = Mapper.Map<BookStoreDto,BookStore>(dto);
bookStore.Name.ShouldEqual("My Store");
bookStore.Address.City.ShouldEqual("Beijing");
bookStore.Books.Count.ShouldEqual(2);
bookStore.Books.First().Title.ShouldEqual("RESTful Web Service");
bookStore.Books.Last().Title.ShouldEqual("Ruby for Rails");
【三】定義類型間的簡易對應規則
前面我們看了Convention的映射方式,客觀的說還是有很多類型間的映射是無法通過簡單的Convention方式來做的,這時候就需要我們使用Configuration了。好在我們的Configuration是在代碼中以“強型別”的方式來寫的,比寫繁瑣易錯的xml方式是要好的多了。
先來看看BookDto到Publisher的映射。
回顧一下前文中定義的規則:BookDto.Publisher -> Publisher.Name。
在AutoMapperzhong,我們可以這樣映射:
C#代碼
var map = Mapper.CreateMap<BookDto,Publisher>();
map.ForMember(d => d.Name, opt => opt.MapFrom(s => s.Publisher));
AutoMapper使用ForMember來指定每一個欄位的映射規則:
引用
The each custom member configuration uses an action delegate to configure each member.
還好有強大的lambda運算式,規則的定義簡單明了。
此外,我們還可以使用ConstructUsing的方式一次直接定義好所有欄位的映射規則。例如我們要定義BookDto到第一作者(Author)的ContactInfo的映射,使用ConstructUsing方式,我們可以:
C#代碼
var map = Mapper.CreateMap<BookDto,ContactInfo>();
map.ConstructUsing(s => new ContactInfo
{
Blog = s.FirstAuthorBlog,
Email = s.FirstAuthorEmail,
Twitter = s.FirstAuthorTwitter
});
然後,就可以按照我們熟悉的方式來使用了:
C#代碼
BookDto dto = new BookDto
{
FirstAuthorEmail = "matt.rogen@abc.com",
FirstAuthorBlog = "matt.amazon.com",
};
ContactInfo contactInfo = Mapper.Map<BookDto, ContactInfo>(dto);
如果需要映射的2個類型有部分欄位名稱相同,又有部分欄位名稱不同呢?還好AutoMapper給我們提供的Convention或Configuration方式並不是“異或的”,我們可以結合使用兩種方式,為名稱不同的欄位配置映射規則,而對於名稱相同的欄位則忽略配置。
例如對於前面提到的AddressDto到Address的映射,假如AddressDto的欄位Country不叫Country叫CountryName,那麼在寫AddressDto到Address的映射規則時,只需要:
C#代碼
var map = Mapper.CreateMap<AddressDto, Address>();
map.ForMember(d => d.Country, opt => opt.MapFrom(s => s.CountryName));
對於City、Street和PostCode無需定義任何規則,AutoMapper仍然可以幫我們進行正確的映射。