初步认识AutoMapper

  • 初步认识AutoMapperhtml

    • 前言app

    • 手动映射ide

    • 使用AutoMapper工具

    • 建立映射spa

    • Conventions插件

    • 映射到一个已存在的实例对象code

 

 

前言

一般在一个应用程序中,咱们开发人员会在两个不一样的类型对象之间传输数据,一般咱们会用DTOs(数据传输对象),View Models(视图模型),或者直接是一些从一个service或者Web API的一些请求或应答对象。一个常见的须要使用数据传输对象的状况是,咱们想把属于一个对象的某些属性值赋值给另外一个对象的某些属性值,可是问题是,这个两个对象可能并非彻底匹配的,好比,二者之间的属性类型,名称等等,是不同的,或者咱们只是想把一个对象的一部分属性值赋值给另外一个对象。orm

 

手动映射

首先,让咱们来看下以前的处理方式,咱们经过如下这个例子来直观感觉这种方式,咱们建立了如下三个类:htm

 

  1. public class Author对象

  2. {

  3. public string Name { get; set; }

  4. }

  5. public class Book

  6. {

  7. public string Title { get; set; }

  8. public Author Author { get; set; }

  9. }

  10. public class BookViewModel

  11. {

  12. public string Title { get; set; }

  13. public string Author { get; set; }

  14. }

为了建立Book对象实例的一个View Model对象实例-BookViewModel对象实例,咱们须要写以下代码:

 

  1. BookViewModel model = new BookViewModel

  2. {

  3. Title = book.Title,

  4. Author = book.Author.Name

  5. }

上面的例子至关的直观了,可是问题也随之而来了,咱们能够看到在上面的代码中,若是一旦在Book对象里添加了一个额外的字段,然后想在前台页面输出这个字段,那么就须要去在项目里找到每一处有这样转换字段的地方,这是很是繁琐的。另外,BookViewModel.Author是一个string类型的字段,可是Book.Author属性倒是Author对象类型的,咱们用的解决方法是经过Book.Auther对象来取得Author的Name属性值,而后再赋值给BookViewModel的Author属性,这样看起行的通,可是想想,若是打算在之后的开发中把Name拆分红两个-FisrtName和LastName,那么,呵呵,咱们得去把原来的ViewModel对象也拆分红对应的两个字段,而后在项目中找到全部的转换,而后替换。 
那么有什么办法或者工具来帮助咱们可以避免这样的状况发生呢?AutoMapper正是符合要求的一款插件。

 

使用AutoMapper

到如今,确切的说,AutoMapper的安装使用很是很是的便捷,就如同傻瓜照相机那样。你只须要从Nuget上下载AutoMapper的包到你的应用程序里,而后添加对AutoMapper命名空间的引用,而后你就能够在你的项目里随意使用它了。如下就是一个很是简单的是例子:

 

  1. AutoMapper.Mapper.CreateMap<Book, BookViewModel>();

  2. var model = AutoMapper.Mapper.Map<BookViewModel>(book);

使用AutoMappeer的好处是显而易见的,首先,再也不须要咱们去对DTO实例的属性一一赋值,而后不管你在Book对象或者BookViewModel对象里加了一个或者更多的字段,那都不会影响这个段映射的代码,我再也不须要去找到每一处转换的地方去更改代码,你的程序会像以前正常运转。 
不过,仍是有个问题并无获得很好的解决,这也是在AutoMapper文档上缺失的,为把Book.Athor.Name字段赋值给BookViewModel.Author字段,须要在每一处须要执行映射的代码地方,同时建立一个以下的显示转换申明代码,因此若是有不少处转换的话,那么咱们就会写不少重复的这几行代码:

 

  1. AutoMapper.Mapper.CreateMap<Book, BookViewModel>()

  2. .ForMember(dest => dest.Author,

  3. opts => opts.MapFrom(src => src.Author.Name));

因此咱们该如何正确的建立映射呢?方式有不少,我这边说下在ASP.NET MVC的程序里如何处理。 
在微软的ASP.NET MVC程序中,它提供了一个Global.asax文件,这个文件里能够放置一些全剧配置,上面对于把Book.Athor.Name字段赋值给BookViewModel.Author字段这个映射配置放置在这个文件里面,那么这段代码只会跑一次可是全部转换的地方都能正确的转换Book.Athor.Name为BookViewModel.Author。固然,Global.asax文件中不建议放很复杂的代码,由于这是ASP.NET程序的入口,一档这个文件里出错,那么整个程序就会over。配置代码能够以这样的形式写,建立一个AutoMapper的配置类:

 

  1. public static class AutoMapperConfig

  2. {

  3. public static void RegisterMappings()

  4. {

  5. AutoMapper.Mapper.CreateMap<Book, BookViewModel>()

  6. .ForMember(dest => dest.Author,

  7. opts => opts.MapFrom(src => src.Author.Name));

  8. }

  9. }

而后再Global文件注册这个类:

 

  1. protected override void Application_Start(object sender, EventArgs e)

  2. {

  3. AutoMapperConfig.RegisterMappings();

  4. }

 

建立映射

全部的映射是有CreateMap方法来完成的:

 

  1. AutoMapper.Mapper.CreateMap<SourceClass, >();

须要注意的是:这种方式是单向的匹配,即在在建立了上面的映射了以后咱们能够在程序里从一个SourceClass实例获得一个DestinationClass类型的对象实例:

 

  1. var destinationClass= AutoMapper.Mapper.Map<DestinationClass>(sourceClass);

可是若是尝试从DestinationClass映射到一个SourceClass,咱们到的是一个错误信息:

 

  1. var book = AutoMapper.Mapper.Map<Book>(bookViewModel);

幸运的是,AutoMapper已经考虑到这个问题了,它提供了ReverseMap方法:

 

  1. AutoMapper.Mapper.CreateMap<Book, BookViewModel>().ReverseMap();

使用了这个方式后你就能够从Book建立BookViewModel,同时也能够从BookViewModel建立Book对象实例。

 

Conventions

AutoMapper之因此能和任何一种集合类型产生交集,是因为它能够配置各类Conventions来完成一个类型到另外一个类型的映射。最基本的一点就是两个映射类型之间的字段名称须要相同。例如一下的一个例子:

 

  1. public class Book

  2. {

  3. public string Title { get; set; }

  4. }

  5. public class NiceBookViewModel

  6. {

  7. public string Title { get; set; }

  8. }

  9. public class BadBookViewModel

  10. {

  11. public string BookTitle { get; set; }

  12. }

若是从Book映射到NiceBookViewModel,那么NiceBookBiewModel的Title属性会被正确设置,可是若是将Book映射为BadBookViewModel,那么BookTitle的属性值将会为NULL值。因此这种状况下,AutoMapper看起来失效了,不过,幸运的是,AutoMapper已经预先考虑到这种状况了,AutoMapper能够经过投影的方式来正确的映射BadBookViewModel和Book,只须要一行代码:

 

  1. AutoMapper.Mapper.CreateMap<Book, BadBookViewModel>()

  2. .ForMember(dest => dest.BookTitle,

  3. opts => opts.MapFrom(src => src.Title));

一种比较复杂的状况的是,当一个类型中引用了另外一个类型的做为其一个属性,例如:

 

  1. public class Author

  2. {

  3. public string Name { get; set; }

  4. }

  5. public class Book

  6. {

  7. public string Title { get; set; }

  8. public Author Author { get; set; }

  9. }

  10. public class BookViewModel

  11. {

  12. public string Title { get; set; }

  13. public string Author { get; set; }

  14. }

虽然Book和BookViewModel都有这一个Author的属性子都,可是它们的类型是不一样,全部若是使用AutoMapper来映射Book的Author到BookViewModel的Author,咱们获得的仍是一个NULL值。对于这种以另外一个类型为属性的映射,AutoMapper内置默认的有个Conventions是会这个的属性名加上这个属性的类型里的属性名称映射到目标类型具备相同名称的字段,即若是在BookViewModel里有一个叫AuthorName的,那么咱们能够获得正确的Name值。可是若是咱们既不想更名称,又想能正确的映射,怎么办呢?Convention就是为此而诞生的:

 

  1. AutoMapper.Mapper.CreateMap<Book, BookViewModel>()

  2. .ForMember(dest => dest.Author,

  3. opts => opts.MapFrom(src => src.Author.Name));

对于AutoMapper,它提供的Conventions功能远不止这些,对于更加复杂的情形,它也可以应对,例如当Author类型的字段有两个属性组成:

 

  1. public class Author

  2. {

  3. public string FirstName { get; set; }

  4. public string LastName { get; set; }

  5. }

可是咱们仍然只想映射到BookViewModel的一个字段,为此,咱们能够这么作:

 

  1. AutoMapper.Mapper.CreateMap<Book, BookViewModel>()

  2. .ForMember(dest => dest.Author,

  3. opts => opts.MapFrom(

  4. src => string.Format("{0} {1}",

  5. src.Author.FirstName,

  6. src.Author.LastName)));

还能够更加复杂,例如:

 

  1. public class Address

  2. {

  3. public string Street { get; set; }

  4. public string City { get; set; }

  5. public string State { get; set; }

  6. public string ZipCode { get; set; }

  7. }

  8. public class Person

  9. {

  10. public string FirstName { get; set; }

  11. public string LastName { get; set; }

  12. public Address Address { get; set; }

  13. }

  14. public class PersonDTO

  15. {

  16. public string FirstName { get; set; }

  17. public string LastName { get; set; }

  18. public string Street { get; set; }

  19. public string City { get; set; }

  20. public string State { get; set; }

  21. public string ZipCode { get; set; }

  22. }

若是从Person映射为PersonDTO,咱们只要想上面同样的作饭就能够了。可是若是这个时候咱们要作的是把PersonDTO映射为Book实体呢?代码实际上是差很少的:

 

  1. AutoMapper.Mapper.CreateMap<PersonDTO, Person>()

  2. .ForMember(dest => dest.Address,

  3. opts => opts.MapFrom(

  4. src => new Address

  5. {

  6. Street = src.Street,

  7. City = src.City,

  8. State = src.State,

  9. ZipCode = src.ZipCode

  10. }));

因此,咱们在Convertion中构建了一个新的Address的实例,而后赋值给Book的Address的属性。 
有时候,咱们可能建立了不止一个DTO来接受映射的结果,例如,对于Address,咱们一样建立了一个AddressDTO:

 

  1. public class AddressDTO

  2. {

  3. public string Street { get; set; }

  4. public string City { get; set; }

  5. public string State { get; set; }

  6. public string ZipCode { get; set; }

  7. }

  8. public class PersonDTO

  9. {

  10. public string FirstName { get; set; }

  11. public string LastName { get; set; }

  12. public AddressDTO Address { get; set; }

  13. }

这个时候若是咱们直接尝试把Person映射为PersonDTO,会报错,映射AutoMapper并不知道Address和AddressDTO之间的映射关系,咱们须要手动建立:

 

  1. AutoMapper.Mapper.CreateMap<PersonDTO, Person>();

  2. AutoMapper.Mapper.CreateMap<AddressDTO, Address>();

 

映射到一个已存在的实例对象

以前咱们都是把映射获得的结果赋值给一个变量,AutoMapper提供了另一种方式,它使得咱们能够直接映射两个已存在的实例。 
以前的作法:

 

  1. AutoMapper.Mapper.CreateMap<SourceClass, DestinationClass>();

  2. var destinationObject = AutoMapper.Mapper.Map<DestinatationClass>(sourceObject);

直接映射的作法:

 

  1. AutoMapper.Mapper.Map(sourceObject, destinationObject);

AutoMapper也支持映射集合对象:

 

  1. var destinationList = AutoMapper.Mapper.Map<List<DestinationClass>>(sourceList);

对于ICollectionIEnumerable的也是一样适用。可是在用AutoMapper来实现内部的集合映射的时候,是很是很是不愉快的,由于AutoMapper会把这个集合做为一个属性来映射赋值,而不是把内置的集合里的一行行内容进行映射,例如对于以下的一个例子:

 

  1. public class Pet

  2. {

  3. public string Name { get; set; }

  4. public string Breed { get; set; }

  5. }

  6. public class Person

  7. {

  8. public List<Pet> Pets { get; set; }

  9. }

  10. public class PetDTO

  11. {

  12. public string Name { get; set; }

  13. public string Breed { get; set; }

  14. }

  15. public class PersonDTO

  16. {

  17. public List<PetDTO> Pets { get; set; }

  18. }

咱们在页面上建立一个更新Pet类型的Name属性的功能,而后提交更新,收到的数据差很少是这样:

 

  1. {

  2. Pets: [

  3. { Name : "Sparky", Breed : null },

  4. { Name : "Felix", Breed : null },

  5. { Name : "Cujo", Breed : null }

  6. ]

  7. }

这个时候若是咱们去将Person映射为PersonDTO:

 

  1. AutoMapper.Mapper.Map(person, personDTO);

咱们获得将是一个全新的Pet的集合,即Name是更新后的数据,可是全部的Breed的值都将为NULL,这个不是所指望的结果。 
很不幸的是,AutoMapper并无提供很好的解决方案。目前能作的一种方案就是用AutoMapper的Ignore方法忽略Pet的属性的映射,而后咱们本身去完成映射:

 

  1. AutoMapper.Mapper.CreateMap<PersonDTO, Person>()

  2. .ForMember(dest => dest.Pets,

  3. opts => opts.Ignore());

 

  1. AutoMapper.Mapper.Map(person, personDTO);

  2. for (int i = 0; i < person.Pets.Count(); i++)

  3. {

  4. AutoMapper.Mapper.Map(person.Pets[i], personDTO.Pets[i]);

  5. }

转自:https://www.cnblogs.com/fred-bao/p/5700776.html

相关文章
相关标签/搜索