注:本系列文章的代码可以在这里下载。
在上一篇文章中我们构造出了完整的应用场景,包括我们的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并传入要映射的类型):
Mapper.CreateMap<AddressDto, Address>();
然后就可以交给AutoMapper帮我们搞定一切了:
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中的相应属性也置为空:
Address address = Mapper.Map<AddressDto,Address>(new AddressDto
{
Country = "China"
});
address.City.ShouldBeNull();
address.Street.ShouldBeNull();
address.PostCode.ShouldBeNull();
甚至如果传入一个空的AddressDto,AutoMapper也会帮我们得到一个空的Address对象。
Address address = Mapper.Map<AddressDto,Address>(null);
address.ShouldBeNull();
千万不要把这种Convention的映射方式当成“玩具”,它在映射具有相同字段名的复杂类型的时候还是具有相当大的威力的。
例如,考虑我们的BookStoreDto到BookStore的映射,两者的字段名称完全相同,只是字段的类型不一致。如果我们定义好了BookDto到Book的映射规则,再加上上述Convention方式的AddressDto到Address的映射,就可以用“零配置”实现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了:
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,我们可以这样映射:
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方式,我们可以:
var map = Mapper.CreateMap<BookDto,ContactInfo>();
map.ConstructUsing(s => new ContactInfo
{
Blog = s.FirstAuthorBlog,
Email = s.FirstAuthorEmail,
Twitter = s.FirstAuthorTwitter
});
然后,就可以按照我们熟悉的方式来使用了:
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的映射规则时,只需要:
var map = Mapper.CreateMap<AddressDto, Address>();
map.ForMember(d => d.Country, opt => opt.MapFrom(s => s.CountryName));
对于City、Street和PostCode无需定义任何规则,AutoMapper仍然可以帮我们进行正确的映射。