使用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项就行了。

 

你可能感兴趣的:(mapper)