Walden1024

导航

使用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  阅读(708)  评论(0编辑  收藏  举报