前言

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

 

手动映射

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

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对象实例的一个View Model对象实例-BookViewModel对象实例,我们需要写如下代码:

BookViewModel model = new BookViewModel
{
Title = book.Title,
Author = book.Author.Name
}

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

 

使用AutoMapper

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

AutoMapper.Mapper.CreateMap<Book, BookViewModel>();
var model = AutoMapper.Mapper.Map<BookViewModel>(book);

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

AutoMapper.Mapper.CreateMap<Book, BookViewModel>()
.ForMember(dest => dest.Author,
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的配置类:

public static class AutoMapperConfig
{
public static void RegisterMappings()
{
AutoMapper.Mapper.CreateMap<Book, BookViewModel>()
.ForMember(dest => dest.Author,
opts => opts.MapFrom(src => src.Author.Name));
}
}

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

protected override void Application_Start(object sender, EventArgs e)
{
AutoMapperConfig.RegisterMappings();
}

创建映射

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

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

需要注意的是:这种方式是单向的匹配,即在在创建了上面的映射了之后我们可以在程序里从一个SourceClass实例得到一个DestinationClass类型的对象实例:

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

但是如果尝试从DestinationClass映射到一个SourceClass,我们到的是一个错误信息:

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

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

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

使用了这个方式后你就可以从Book创建BookViewModel,同时也可以从BookViewModel创建Book对象实例。

 

Conventions

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

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,那么NiceBookBiewModel的Title属性会被正确设置,但是如果将Book映射为BadBookViewModel,那么BookTitle的属性值将会为NULL值。所以这种情况下,AutoMapper看起来失效了,不过,幸运的是,AutoMapper已经预先考虑到这种情况了,AutoMapper可以通过投影的方式来正确的映射BadBookViewModel和Book,只需要一行代码:

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,我们得到的还是一个NULL值。对于这种以另一个类型为属性的映射,AutoMapper内置默认的有个Conventions是会这个的属性名加上这个属性的类型里的属性名称映射到目标类型具有相同名称的字段,即如果在BookViewModel里有一个叫AuthorName的,那么我们可以得到正确的Name值。但是如果我们既不想改名称,又想能正确的映射,怎么办呢?Convention就是为此而诞生的:

AutoMapper.Mapper.CreateMap<Book, BookViewModel>()
.ForMember(dest => dest.Author,
opts => opts.MapFrom(src => src.Author.Name));

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

public class Author
{
public string FirstName { get; set; }
public string LastName { get; set; }
}

但是我们仍然只想映射到BookViewModel的一个字段,为此,我们可以这么做:

AutoMapper.Mapper.CreateMap<Book, BookViewModel>()
.ForMember(dest => dest.Author,
opts => opts.MapFrom(
src => string.Format("{0} {1}",
src.Author.FirstName,
src.Author.LastName)));

还可以更加复杂,例如:

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

如果从Person映射为PersonDTO,我们只要想上面一样的做饭就可以了。但是如果这个时候我们要做的是把PersonDTO映射为Book实体呢?代码其实是差不多的:

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
}));

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

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

这个时候如果我们直接尝试把Person映射为PersonDTO,会报错,映射AutoMapper并不知道Address和AddressDTO之间的映射关系,我们需要手动创建:

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

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

之前我们都是把映射得到的结果赋值给一个变量,AutoMapper提供了另外一种方式,它使得我们可以直接映射两个已存在的实例。 
之前的做法:

   AutoMapper.Mapper.CreateMap<SourceClass, DestinationClass>();
 var destinationObject = AutoMapper.Mapper.Map<DestinatationClass>(sourceObject);

直接映射的做法:

 AutoMapper.Mapper.Map(sourceObject, destinationObject);

AutoMapper也支持映射集合对象:

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

对于ICollectionIEnumerable的也是同样适用。但是在用AutoMapper来实现内部的集合映射的时候,是非常非常不愉快的,因为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; }
}

我们在页面上创建一个更新Pet类型的Name属性的功能,然后提交更新,收到的数据差不多是这样:

{
Pets: [
{ Name : "Sparky", Breed : null },
{ Name : "Felix", Breed : null },
{ Name : "Cujo", Breed : null }
]
}

这个时候如果我们去将Person映射为PersonDTO:

 AutoMapper.Mapper.Map(person, personDTO);

我们得到将是一个全新的Pet的集合,即Name是更新后的数据,但是所有的Breed的值都将为NULL,这个不是所期望的结果。 
很不幸的是,AutoMapper并没有提供很好的解决方案。目前能做的一种方案就是用AutoMapper的Ignore方法忽略Pet的属性的映射,然后我们自己去完成映射:

 
AutoMapper.Mapper.CreateMap<PersonDTO, Person>()
.ForMember(dest => dest.Pets,
opts => opts.Ignore());
AutoMapper.Mapper.Map(person, personDTO);
for (int i = ; i < person.Pets.Count(); i++)
{
AutoMapper.Mapper.Map(person.Pets[i], personDTO.Pets[i]);
}

【来龙去脉系列】AutoMapper一款自动映射框架的更多相关文章

  1. AutoMapper自动映射

    十年河东,十年河西,莫欺少年穷. 学无止境,精益求精. 不扯犊子,直接进入正题: AutoMapper自动映射常用于EF中,能很好的解决DTO和Model之间相互映射的问题.在未使用AutoMappe ...

  2. Sql Server来龙去脉系列之二 框架和配置

    本节主要讲维持数据的元数据,以及数据库框架结构.内存管理.系统配置等.这些技术点在我们使用数据库时很少接触到,但如果要深入学习Sql Server这一章节也是不得不看.本人能力有限不能把所有核心的知识 ...

  3. automapper 自动映射 集成asp.net Core2.1

    学习博文:https://www.cnblogs.com/yan7/p/8085410.html 1.使用nuget 安装 <PackageReference Include="Aut ...

  4. Asp.NetCore 3.1 使用AutoMapper自动映射转换实体 DTO,Data2ViewModel

    1:什么是AutoMapper? 下面为AutoMapper官方的解释: AutoMapper是一个对象-对象映射器.对象-对象映射通过将一种类型的输入对象转换为另一种类型的输出对象来工作. 使Aut ...

  5. C# AutoMapper:流行的对象映射框架,可减少大量硬编码,很小巧灵活,性能表现也可接受。

    AutoMapper 是一个对象-对象映射器,可以将一个对象映射到另一个对象. 官网地址:http://automapper.org/ 官方文档:https://docs.automapper.org ...

  6. autoMapper dotnetcore webapi 自动添加映射 abp

    在ef的xxxxApplicationModule的Initialize方法中,已经添加了自动映射的配置,如下图: 写的很明白了,我们只需要写一个类继承Profile就可以了.如下图所示: 这样就可以 ...

  7. mybatis框架-resultMap的自动映射级别-partial 和full的探讨

    现在我们做一个小实验,输出一下上一个案例中没有匹配的属性,注意哦,现在user类中是有内部嵌套的复杂数据类型的 运行结果: 注意到:现在居然连userPassword都打印不出来了,原因就是user类 ...

  8. Mybatis框架-resultMap元素的自动映射级别

    resultMap的自动映射级别:分为三种:NONE  PARTIAL  FULL 其中默认的属性是:PARTIAL:开启自动匹配,会自动匹配数据库中的字段名和实体类中的属性名,如果一致,就能匹配上, ...

  9. Sql Server来龙去脉系列之四 数据库和文件

        在讨论数据库之前我们先要明白一个问题:什么是数据库?     数据库是若干对象的集合,这些对象用来控制和维护数据.一个经典的数据库实例仅仅包含少量的数据库,但用户一般也不会在一个实例上创建太多 ...

随机推荐

  1. Expo大作战(六)--expo开发模式,expo中exp命令行工具,expo中如何查看日志log,expo中的调试方式

    简要:本系列文章讲会对expo进行全面的介绍,本人从2017年6月份接触expo以来,对expo的研究断断续续,一路走来将近10个月,废话不多说,接下来你看到内容,将全部来与官网 我猜去全部机翻+个人 ...

  2. qtcreator minggw 支持c++11

    pro文件添加 QMAKE_CXXFLAGS += -std=c++11

  3. HttpWebRequest抓取网页内容与直接输入URL得到的内容不一致!球大神帮忙!!

    一.前言 我在做一个百度收录情况查询的软件,就是通过软件来批量查询文章链接是否被百度收录,主要是用来查询某个网址的收录次数还有网站的排行数,思路是借鉴别人的. 二.问题描述 首先需要考虑的是能够支持哪 ...

  4. 判断Exception类中是否有InnerException属性

    public static class ExceptionExtend { /// <summary> /// 利用反射来判断对象是否包含某个属性 /// </summary> ...

  5. 匿名访问windows server 2008 R2 文件服务器的共享

    匿名访问windows server 2008 R2 文件服务器的共享 匿名访问windows 2008 R2 文件服务器的共享,七步:第一步 取消简单文件共享:第二步 设置需要共享的文件夹every ...

  6. MySQL面试题36道

    MySQL数据库是在免费的数据库中最受欢迎的一款,尤其是在一些小型项目以及项目资金有限的情况下,选择MySQL来作为数据存储的工具,那些不差钱并且数据吞吐量非常大的互联网公司一般都是会用付费的Orac ...

  7. 乘风破浪:LeetCode真题_030_Substring with Concatenation of All Words

    乘风破浪:LeetCode真题_030_Substring with Concatenation of All Words 一.前言    这次我们还是找字符串的索引,不过,需要将另一个字符串列表中的 ...

  8. Docker容器学习与分享08

    Docker容器网络 Docker除了默认创建的三种网络外,还可以自定义网络. 首先创建一个bridge类型的网络,使用docker network create命令. [root@promote ~ ...

  9. Django商城项目笔记No.9用户部分-注册接口签发JWTtoken

    Django商城项目笔记No.9用户部分-注册接口签发JWTtoken 我们在验证完用户的身份后(检验用户名和密码),需要向用户签发JWT,在需要用到用户身份信息的时候,还需核验用户的JWT. 关于签 ...

  10. SVN 图标不显示的解决办法

    SVN 的图标没办法显示了.经搜索,发现需要修改注册表 HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Sh ...