在上一篇文章中我們構造出了完整的應用場景,包括我們的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>();
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 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();
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();
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>();
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");
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));
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
});
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 = "[email protected]",
FirstAuthorBlog = "matt.amazon.com",
};
ContactInfo contactInfo = Mapper.Map<BookDto, ContactInfo>(dto);
BookDto dto = new BookDto
{
FirstAuthorEmail = "[email protected]",
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));
var map = Mapper.CreateMap<AddressDto, Address>();
map.ForMember(d => d.Country, opt => opt.MapFrom(s => s.CountryName));
對於City、Street和PostCode無需定義任何規則,AutoMapper仍然可以幫我們進行正確的映射。