使用AutoMapper
一、AutoMapper初探:
[参考Using AutoMapper: Getting Started]
1.新建空的ASP.NET MVC项目。
2.在Models文件夹添加类:
public class Book { public string Title { get; set; } }
public class BookViewModel { public string Title { get; set; } }
3.安装AtuoMapper:
Install-Package AutoMapper
4.在App_Start文件夹添加配置类:
public static class AutoMapperConfig { public static void RegisterMappings() { AutoMapper.Mapper.CreateMap<Book, BookViewModel>(); } }
5.在Global.asax中注册:
AutoMapperConfig.RegisterMappings();
6.新建HomeController:
public string Index() { var book = new Book{Title="水浒传" }; var bookModel = AutoMapper.Mapper.Map<Book>(book); return bookModel.Title; }
7.运行程序:
二、创建映射:
[参考Using AutoMapper: Creating Mappings]
1.CreateMap方法:
AutoMapper所有映射都是使用CreateMap方法定义的:
AutoMapper.Mapper.CreateMap<SourceClass, DestinationClass>();
需要注意的是,上面定义的映射是单向映射。例如,我们定义了如下映射:
AutoMapper.Mapper.CreateMap<Book, BookViewModel>();
我们可以将一个Book实例映射到一个BookViewModel实例:
var bookViewModel = AutoMapper.Mapper.Map<BookViewModel>(book);
但是我们不能将一个BookViewModel实例映射到一个Book实例:
var book = AutoMapper.Mapper.Map<Book>(bookViewModel);
如果要实现双向映射,需要再次调用CreateMap方法定义:
AutoMapper.Mapper.CreateMap<Book, BookViewModel>();
AutoMapper.Mapper.CreateMap<BookViewModel, Book>();
如果我们不需要为反向映射定义任何自定义映射逻辑,我们可以使用ReverseMap实现双向映射:
AutoMapper.Mapper.CreateMap<Book, BookViewModel>().ReverseMap();
2.映射规则:
AutoMapper有一些映射约定,其中一个约定就是同名映射。例如:
public class Book { public string Title { get; set; } } public class NiceBookViewModel { public string Title { get; set; } } public class BadBookViewModel { public string BookTitle { get; set; } }
如果我们将一个Book实例映射到一个NiceBookViewModel实例,Title属性的值会是我们所期望的值。然而如果我们将一个Book实例映射到一个BadBookViewModel实例,Title属性的值会为null。因为名字不同,AutoMapper无从知晓BookTitle需要从Title获取值。这种情况下就需要我们手动添加配置代码:
AutoMapper.Mapper.CreateMap<Book, BadBookViewModel>() .ForMember(dest => dest.BookTitle, opts => opts.MapFrom(src => src.Title));
另一个约定涉及到嵌入对象。例如:
public class Author { public string Name { get; set; } } public class Book { public string Title { get; set; } public Author Author { get; set; } } public class BookViewModel { public string Title { get; set; } public string Author { get; set; } }
尽管Book和BookViewModel都有Author属性,但是它们的类型不匹配,因此AutoMapper不能将Book.Author映射到BookViewModel.Author。对于嵌入对象,AutoMapper的约定是目标类属性的命名为骆驼拼写法的“对象名+对象属性名”。示例如下:
public class BookViewModel { public string Title { get; set; } public string AuthorName { get; set; } }
如果不采用约定,我们依然可以使用配置代码:
AutoMapper.Mapper.CreateMap<Book, BookViewModel>() .ForMember(dest => dest.Author, opts => opts.MapFrom(src => src.Author.Name));
3.投影:
在之前的示例中,如果我们把Author类修改为:
public class Author { public string FirstName { get; set; } public string LastName { get; set; } }
我们需要将这两个属性映射到一个属性,代码如下:
AutoMapper.Mapper.CreateMap<Book, BookViewModel>() .ForMember(dest => dest.Author, opts => opts.MapFrom( src => string.Format("{0} {1}", src.Author.FirstName, src.Author.LastName)));
4.更复杂的投影:
public class Address { public string Street { get; set; } public string City { get; set; } public string State { get; set; } public string ZipCode { get; set; } } public class Person { public string FirstName { get; set; } public string LastName { get; set; } public Address Address { get; set; } } public class PersonDTO { public string FirstName { get; set; } public string LastName { get; set; } public string Street { get; set; } public string City { get; set; } public string State { get; set; } public string ZipCode { get; set; } }
如果我们要把PersonDTO映射到Person,代码如下:
AutoMapper.Mapper.CreateMap<PersonDTO, Person>() .ForMember(dest => dest.Address, opts => opts.MapFrom( src => new Address { Street = src.Street, City = src.City, State = src.State, ZipCode = src.ZipCode }));
5.嵌套映射:
上面的示例中,如果PersonDTO修改为:
public class AddressDTO { public string Street { get; set; } public string City { get; set; } public string State { get; set; } public string ZipCode { get; set; } } public class PersonDTO { public string FirstName { get; set; } public string LastName { get; set; } public AddressDTO Address { get; set; } }
映射应该修改为:
AutoMapper.Mapper.CreateMap<PersonDTO, Person>();
AutoMapper.Mapper.CreateMap<AddressDTO, Address>();
三、映射到实例:
[参考Using AutoMapper: Mapping Instances]
1.映射到新的实例:
之前的示例均产生一个新的实例,例如:
var destinationObject = AutoMapper.Mapper.Map<DestinatationClass>(sourceObject);
2.映射到已经存在的实例:
AutoMapper.Mapper.Map(sourceObject, destinationObject);
3.映射到集合:
var destinationList = AutoMapper.Mapper.Map<List<DestinationClass>>(sourceList);
我们可以映射到所有的集合类型和接口:List<T>、ICollection<T>、IEnumerable<T>等。
但是如果我们尝试映射到现有的实例:
AutoMapper.Mapper.Map(sourceList, destinationList);
我们会发现destinationList已经被损坏。因为AutoMapper实际上是映射到集合而不是分别映射到集合中的对象。当我们考虑对象的层级结构时,这种情况就变得十分讨厌。例如:
public class Pet { public string Name { get; set; } public string Breed { get; set; } } public class Person { public List<Pet> Pets { get; set; } } public class PetDTO { public string Name { get; set; } public string Breed { get; set; } } public class PersonDTO { public List<PetDTO> Pets { get; set; } }
如果我们创建一个只更新Name属性的表单,我们提交的对象图会是这样:
{
Pets: [
{ Name : "Sparky", Breed : null },
{ Name : "Felix", Breed : null },
{ Name : "Cujo", Breed : null }
]
}
由于Breed属性没有被传递,它在每个PetDTO中的值为null。现在如果我们使用PersonDTO更新Person:
AutoMapper.Mapper.Map(person, personDTO);
这样Person中每个Pet的Breed属性均变为null。这种问题解决起来有点麻烦,首先我们需要使用Ignore方法:
AutoMapper.Mapper.CreateMap<PersonDTO, Person>() .ForMember(dest => dest.Pets, opts => opts.Ignore());
上面的代码告诉AutoMapper不映射Pets集合,这就意味着我们现在必须手动处理:
AutoMapper.Mapper.Map(person, personDTO); for (int i = 0; i < person.Pets.Count(); i++) { AutoMapper.Mapper.Map(person.Pets[i], personDTO.Pets[i]); }
上面的代码是基于假设两个list是相同的即我们没有对它们重新排序,并且不允许在表单添加或者删除项。为了解决重新排序后不一致的问题,我们需要依赖某些标识属性。例如:
var pet = person.Pets[i]; var updatedPet = personDTO.Pets.Single(m => m.Id == pet.Id); AutoMapper.Mapper.Map(pet, updatedPet);
添加或者删除项会更复杂一些:
var updatedPets = new List<Pet>(); foreach (var pet in personDTO.Pets) { var existingPet = person.Pets.SingleOrDefault(m => m.Id == pet.Id); // No existing pet with this id, so add a new one if (existingPet == null) { updatedPets.Add(AutoMapper.Mapper.Map<Pet>(pet)); } // Existing pet found, so map to existing instance else { AutoMapper.Mapper.Map(existingPet, pet); updatedPets.Add(existingPet); } } // Set pets to updated list (any removed items drop out naturally) person.Pets = updatedPets;
这可能是使用AutoMapper最大的难点,但是只要我们在执行更新操作的时候记得手动映射list项就行了。
posted on 2015-07-01 15:53 Walden1024 阅读(707) 评论(0) 编辑 收藏 举报