[.NET] - 初步认识AutoMapper

初步认识AutoMapper



AutoMapper

  • 初步认识AutoMapper

    • 前言
    • 手动映射
    • 使用AutoMapper
    • 创建映射
    • Conventions
    • 映射到一个已存在的实例对象

前言

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

手动映射

首先,让我们来看下之前的处理方式,我们通过以下这个例子来直观感受这种方式,我们创建了以下三个类:

  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. }

译自:http://cpratt.co/using-automapper-getting-started/

时间: 2024-10-12 18:29:30

[.NET] - 初步认识AutoMapper的相关文章

ioc初步理解(二) 简单实用autofac搭建mvc三层+automapper=》ioc(codeFirst)

之前在园子闲逛的时候,发现许多关于automapper的文章,以及用aotufac+automapper合在一起用.当然发现大多数文章是将automapper的特点说出或将automapper几处关键代码放出.当然有过基础的人看这种文章没有什么问题,但是对于完全没有基础的小白来看完全是灾难级别的(我就是),经常按着博文一点一点的写,突然发现少了一部分导致代码无法运行.在搜索各种文章之后,终于用自己的理解写出了一个简单的运用automapper的demo,日后待我对automapper理解加深会进

你不知道的事-建站始末1【准备篇】

阅读目录 建站四部曲: 你不知道的事-建站始末1[准备篇] 你不知道的事-建站始末2[框架篇] 你不知道的事-建站始末3[实现篇] 你不知道的事-建站始末4[总结篇] 本篇目录: 写在前面 为什么要建站? 建站计划 前端总结 知识点总结 域名和服务器 关于开源 未完待续 本篇内容会有些长,希望各位看官可以认真的阅读下去,我相信肯定会有收获. 写在前面 蝴蝶眨几次眼睛,才学会飞行,夜空洒满了星星,但几颗会落地. --你不知道的事 蝴蝶眨眼睛?星星会落地?当然很多人会认为这是无稽之谈,但是有些人却认

automapper初步

首先引入 automapper.dll using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace AutoMapper { class Program { static void Main(string[] args) { var order = new Order() { Id=1, OrderName="F", ClientName ="A

【G】开源的分布式部署解决方案(三) - 一期规划定稿与初步剖析

G.系列导航 [G]开源的分布式部署解决方案 - 预告篇 [G]开源的分布式部署解决方案(一) - 开篇 [G]开源的分布式部署解决方案(二) - 好项目是从烂项目基础上重构出来的 [G]开源的分布式部署解决方案(三) - 一期规划定稿与初步剖析 抱歉 首先我先说声抱歉,因为上一篇结尾预告第三篇本该是“部署项目管理”,那为什么变成本篇呢? 请容我解释一下,在预告篇到现在为止,经常会有人问我这个项目到底是干什么的.或许之前写的比较粗糙.那我相信目前定稿后的功能概览图应该会给大家一个比较清晰的认识.

Asp.net 面向接口可扩展框架之使用“类型转化基础服务”测试四种Mapper(AutoMapper、EmitMapper、NLiteMapper及TinyMapper)

Asp.net 面向接口可扩展框架的“类型转化基础服务”是我认为除了“核心容器”之外最为重要的组成部分 但是前面博文一出,争议很多,为此我再写一篇类型转化基础服务和各种Mapper结合的例子,顺便对各种Mapper做个简单的优缺点对比 我对第三方组件评介有三个标准,一.可用性,二.性能,三.易用性 本例子中四个四种Mapper以前我都没使用过(因为以前我都用自己的Mapper),本次测试可能不准确,错误的地方请大家指正 AutoMapper使用的是4.2.1.0,需要.net4.5支持(我使用N

初步了解CPU

了解CPU By JackKing_defier 首先说明一下,本文内容主要是简单说明CPU的大致原理,所需要的前提知识我会提出,但是由于篇幅我不会再详细讲解需要的其他基础知识.默认学过工科基础课. 一.总述 先从计算机的结构说起,在现代计算机中,CPU是核心,常常被比喻为人的大脑.现在的计算机都为“冯·诺依曼机”,“冯诺依曼机”的一个显著的特点就是由运算器.存储器.控制器.输入设备和输出设备组成.CPU是运算器和控制器合起来的统称,因为运算器和控制器在逻辑关系和电路结构上联系十分紧密,尤其在大

zerglurker的C语言教程004——指针初步讲解

在上次的教程里面,我提到了指针. 针对指针,这次我将简单的讲讲,后面我还会讲到--那个时候你应该有了相当的基础. 首先,先讲讲指针类型. 任何类型关键字后面加一个*符号,就会变成指针类型. 比如: char → char* 字符指针 int → int* 整数指针 double→double* 双精度指针 甚至还可以这样: char*→char** 字符指针的指针类型 →char*** 字符指针的指针的指针类型- 指针本质上是一个内存地址值,该内存地址上存放的是相关类型的数值.但是void*指针

使用AutoMapper实现Dto和Model的自由转换(上)

在实际的软件开发项目中,我们的"业务逻辑"常常需要我们对同样的数据进行各种变换.例如,一个Web应用通过前端收集用户的输入成为Dto,然后将Dto转换成领域模型并持久化到数据库中.另一方面,当用户请求数据时,我们又需要做相反的工作:将从数据库中查询出来的领域模型以相反的方式转换成Dto再呈现给用户.有时候我们还会面临更多的数据使用需求,例如有多个数据使用的客户端,每个客户端都有自己对数据结构的不同需求,而这也需要我们进行更多的数据转换. 频繁的数据转换琐碎而又凌乱,很多时候我们不得不:

使用AutoMapper实现Dto和Model的自由转换(中)

在上一篇文章中我们构造出了完整的应用场景,包括我们的Model.Dto以及它们之间的转换规则.下面就可以卷起袖子,开始我们的AutoMapper之旅了. [二]以Convention方式实现零配置的对象映射 我们的AddressDto和Address结构完全一致,且字段名也完全相同.对于这样的类型转换,AutoMapper为我们提供了Convention,正如它的官网上所说的: 引用 AutoMapper uses a convention-based matching algorithm to