在 ASP.NET Core 项目中使用 AutoMapper 进行实体映射


 一、前言

  在实际项目开发过程中,我们使用到的各种 ORM 组件都可以很便捷的将我们获取到的数据绑定到对应的 List<T> 集合中,因为我们最终想要在页面上展示的数据与数据库实体类之间可能存在很大的差异,所以这里更常见的方法是去创建一些对应于页面数据展示的 `视图模型` 类,通过对获取到的数据进行二次加工,从而满足实际页面显示的需要。

  因此,如何更便捷的去实现 数据库持久化对象 与 视图对象 间的实体映射,避免我们在代码中去一次次的手工实现这一过程,就可以降低开发的工作量,而 AutoMapper 则是可以帮助我们便捷的实现实体转换这一过程的利器。所以,本章我们就来学习如何在 ASP.NET Core 项目中通过使用 AutoMapper 去完成实体间的映射。

  当然,如果你习惯于从视图展现到持久化到数据库都采用数据库实体,那么本篇文章对你可能不会有任何的帮助。

  代码仓储:https://github.com/Lanesra712/grapefruit-common/tree/master/sample/aspnetcore/aspnetcore-automapper-tutorial

 二、Step by Step

  AutoMapper 是一个 OOM(Object-Object-Mapping) 组件,从名字上就可以看出来,这一系列的组件主要是为了帮助我们实现实体间的相互转换,从而避免我们每次都采用手工编写代码的方式进行转换。在没有采用 OOM 组件之前,如果我们需要实现类似于一份数据在不同客户端显示不同的字段,我们只能以手工的、逐个属性赋值的方式实现数据在各个客户端数据类型间的数据传递,而 OOM 组件则可以很方便的帮我们实现这一需求。

  1、几个概念

  在上面我们有提到 数据库持久化对象 和 视图对象 这两个概念,其实除了这两个对象的概念之外,还存在一个 数据传输对象 的概念,这里我们来简单阐述下这三种对象的概念。

  数据库持久化对象(Persistent Object):顾名思义,这个对象是用来将我们的数据持久化到数据库,一般来说,持久化对象中的字段会与数据库中对应的 table 保持一致。

  这里,如果你采用了 DDD 的思想去指导设计系统架构,其实最终落地到我们代码中的其实是 领域对象(Domain Object),它与 数据库持久化对象 最显著的差异在于 领域对象 会包含当前业务领域的各种事件,而 数据库持久化对象 仅是包含了数据库中对应 table 的数据字段信息。

  视图对象(View Object):视图对象 VO 是面向前端用户页面的,一般会包含呈现给用户的某个页面/组件中所包含的所有数据字段信息。

  数据传输对象(Data Transfer Object):数据传输对象 DTO 一般用于前端展示层与后台服务层之间的数据传递,以一种媒介的形式完成 数据库持久化对象 与 视图对象 之间的数据传递。

  这里通过一个简单的示意图去解释下这三种对象的具体使用场景,在这个示例的项目中,我省略了数据传输对象,将数据库持久化对象直接转换成页面显示的视图对象。

640?wx_fmt=png

  2、组件加载

  首先我们需要通过 Nuget 将 AutoMapper 加载到项目中,因为这个示例项目只包含一个 MVC 的项目,并没有多余的分层,所以这里需要将两个使用到的 dll 都添加到这个 MVC 项目中。

Install-Package AutoMapper
Install-Package AutoMapper.Extensions.Microsoft.DependencyInjection

640?wx_fmt=png

  这里我添加了 AutoMapper.Extensions.Microsoft.DependencyInjection 这个程序集,从这个程序集的名字就可以看出来,这个程序集主要是为了我们可以通过依赖注入的方式在项目中去使用 AutoMapper。

  在 .NET Fx 的时代,我们使用 AutoMapper 时,可能就像下面的代码一样,更多的是通过 Mapper 的几个静态方法来实现实体间的映射,不过在 .NET Core 程序中,我们首选还是采用依赖注入的方式去完成实体间的映射。

// 构建实体映射规则
Mapper.Initialize(cfg => cfg.CreateMap<OrderModel, OrderDto>());

// 实体映射
var order = new OrderModel{};
OrderDto dto = Mapper.Map<OrderModel,OrderDto>(order);

  3、使用案例

  因为原本想要使用的示例项目是之前的 ingos-server 这个项目,由于目前自己有在学习 DDD 的知识,并且有在按照微软的 eShopOnContainers 这个项目中基于 DDD 思想设计的框架,对自己的这个 ingos-server 项目进行 DDD 化的调整,嗯,其实就是照葫芦画瓢,所以目前整个项目被我改的乱七八糟的,不太适合作为示例项目了,所以这里新创建了一个比较单纯的 ASP.NET Core MVC 项目来作为这篇文章的演示项目。

  因为这个示例项目只是为了演示如何在 ASP.NET Core 项目中去使用 AutoMapper,所以这里并没有进行分层,整个示例页面的运行流程就是,PostController 中的 List Action 调用 PostAppService 类中的 GetPostLists 方法去获取所有的文章数据,同时在这个方法中会进行实体映射,将我们从 PostDomain 中获取到的 PO 对象转换成页面展示的 VO 对象,项目中每个文件夹的作用见下图所示。

640?wx_fmt=png

  这里的示例项目是演示当我们从数据库获取到需要的数据后,如何完成从 PO 到 VO 的实体映射,PostModel(PO)和 PostViewModel(VO)的类定义如下所示。


public class PostModel
{
public Guid Id { get; set; }
public long SerialNo { get; set; }
public string Title { get; set; }
public string Author { get; set; }
public string Image { get; set; }
public short CategoryCode { get; set; }
public bool IsDraft { get; set; }
public string Content { get; set; }
public DateTime ReleaseDate { get; set; }
public virtual IList<CommentModel> Comments { get; set; }
}

public class PostViewModel
{
public Guid Id { get; set; }
public long SerialNo { get; set; }
public string Title { get; set; }
public string Author { get; set; }
public short CategoryCode { get; set; }
public string Category => CategoryCode == 1001 ? ".NET" : "杂谈";
public string ReleaseDate { get; set; }
public short CommentCounts { get; set; }
public virtual int Count { get; set; }
}


  首先我们需要创建一个实体映射的配置类,需要继承于 AutoMapper 的 Profile 类,在无参构造函数中,我们就可以通过 CreateMap 方法去创建两个实体间的映射关系。


public class PostProfile : Profile
{
/// <summary>
/// ctor
/// </summary>
public PostProfile()
{
// 配置 mapping 规则
//
CreateMap<PostModel, PostViewModel>();
}
}


640?wx_fmt=png

  通过泛型的 CreateMap 方法就可以完成我们从 PostModel(PO) 到 PostViewModel(VO) 的实体映射。当然,因为 AutoMapper 默认是通过匹配字段名称和类型进行自动匹配,所以如果你进行转换的两个类的中的某些字段名称不一样,这里我们就需要进行手动的编写转换规则。

  就像在这个需要进行实体映射的示例代码中,PostViewModel 中的 CommentCounts 字段是根据 PostModel 中 CommentModel 集合的数据个数进行赋值的,所以这里我们就需要对这个字段的转换规则进行修改。

  在 AutoMapper 中,我们可以通过 ForMember 方法对映射规则做进一步的加工。这里我们需要指明 PostViewModel 的 CommentCounts 字段的值是通过对 PostModel 中的 Comments 信息进行求和从而获取到的,最终实现的转换代码如下所示。


public class PostProfile : Profile
{
/// <summary>
/// ctor
/// </summary>
public PostProfile()
{
// 配置 mapping 规则
//
CreateMap<PostModel, PostViewModel>()
.ForMember(destination => destination.CommentCounts, source => source.MapFrom(i => i.Comments.Count()));
}
}

  ForMember 方法不仅可以进行指定不同名称的字段进行转换,也可以通过编写规则实现字段类型的转换。例如这里 PO 中的 ReleaseDate 字段其实是 DateTime 类型的,我们需要通过编写规则将该字段对应到 VO 中 string 类型的 ReleaseDate 字段上,最终的实现代码如下所示。


public class PostProfile : Profile
{
/// <summary>
/// ctor
/// </summary>
public PostProfile()
{
// Config mapping rules
//
CreateMap<PostModel, PostViewModel>()
.ForMember(destination => destination.CommentCounts, source => source.MapFrom(i => i.Comments.Count()))
.ForMember(destination => destination.ReleaseDate, source => source.ConvertUsing(new DateTimeConverter()));
}
}

public class DateTimeConverter : IValueConverter<DateTime, string>
{
public string Convert(DateTime source, ResolutionContext context)
=> source.ToString("yyyy-MM-dd HH:mm:ss");
}


  这里很多人可能习惯将所有的实体映射规则都放到同一个 Profile 文件里面,因为这里采用是单体架构的项目,所以整个项目中会存在不同的模块,所以这里我是按照每个模块去创建对应的 Profile 文件。实际在 ingos-server 这个项目中的使用方式见下图所示。

   当我们创建好对应的映射规则后,因为我们是采用依赖注入的方式进行使用,所以这里我们就需要将我们的匹配规则注入到 IServiceCollection 中。从之前加载的程序集的 github readme 描述中可以看到,我们需要将配置好的 Profile 类通过 AddAutoMapper 这个扩展方法进行注入。

  因为我们在实际项目中可能存在多个自定义的 Profile 文件,而我们肯定是需要将这些自定义规则都注入到 IServiceCollection 中。所以我在 AddAutoMapper 这个方法的基础上创建了一个 AddAutoMapperProfiles 方法去注入我们的实体映射规则。

  通过 AutoMapper 的说明我们可以看出来,所有的自定义的 Profile 类都是需要继承于 AutoMapper 的 Profile 基类,所以这里我是采用反射的方式,通过获取到程序集中所有继承于 Profile 类的类文件进行批量的注入到 IServiceCollection 中,具体的实现代码如下所示。

/// <summary>
/// Automapper 映射规则配置扩展方法
/// </summary>
public static class AutoMapperExtension
{
public static IServiceCollection AddAutoMapperProfiles(this IServiceCollection services)
{
// 从 appsettings.json 中获取包含配置规则的程序集信息
string assemblies = ConfigurationManager.GetConfig("Assembly:Mapper");

if (!string.IsNullOrEmpty(assemblies))
{
var profiles = new List<Type>();

// 获取继承的 Profile 类型信息
var parentType = typeof(Profile);

foreach (var item in assemblies.Split(new char[] { '|' }, StringSplitOptions.RemoveEmptyEntries))
{
// 获取所有继承于 Profile 的类
//
var types = Assembly.Load(item).GetTypes()
.Where(i => i.BaseType != null && i.BaseType.Name == parentType.Name);

if (types.Count() != 0 || types.Any())
profiles.AddRange(types);
}

// 添加映射规则
if (profiles.Count() != 0 || profiles.Any())
services.AddAutoMapper(profiles.ToArray());
}

return services;
}
}

  因为我是将需要加载的程序集信息放到配置文件中的,所以这里我们只需要将包含 Profile 规则的程序集添加到对应的配置项下面就可以了,此时如果包含多个程序集,则需要使用 `|` 进行分隔。

{
"Assembly": {
"Mapper": "aspnetcore-automapper-tutorial"
}
}

  当我们将所有的实体映射规则注入到 IServiceCollection 中,就可以在代码中使用这些实体映射规则。和其它通过依赖注入的接口使用方式相同,我们只需要在使用到的地方注入 IMapper 接口,然后通过 Map 方法就可以完成实体间的映射,使用的代码如下。

public class PostAppService : IPostAppService

{
#region Initialize

/// <summary>
///
/// </summary>
private readonly IPostDomain _post;

/// <summary>
///
/// </summary>
private readonly IMapper _mapper;

/// <summary>
/// ctor
/// </summary>
/// <param name="post"></param>
/// <param name="mapper"></param>
public PostAppService(IPostDomain post, IMapper mapper)
{
_post = post ?? throw new ArgumentNullException(nameof(post));
_mapper = mapper ?? throw new ArgumentNullException(nameof(mapper));
}

#endregion Initialize

/// <summary>
/// 获取所有的文章信息
/// </summary>
/// <returns></returns>
public IList<PostViewModel> GetPostLists()
{
var datas = _post.GetPostLists();
return _mapper.Map<IList<PostModel>, IList<PostViewModel>>(datas);
}
}

  至此我们就实现了在 ASP.NET Core 项目中使用 AutoMapper,实现后的结果如下图所示。

640?wx_fmt=gif

 三、总结

  本篇文章主要是演示下如何在 ASP.NET Core 项目中去使用 AutoMapper 来实现实体间的映射,因为之前只是在 .NET Fx 项目中有使用过这个组件,并没有在 .NET Core 项目中使用,所以这次趁着国庆节假期就来尝试如何在 .NET Core 项目中使用,整个组件使用起来其实是很简单的,但是使用后却可以给我们在实际的项目开发中省很多的事,所以就把自己的使用方法分享出来,如果对你有些许的帮助的话,不胜荣幸~~~

原文链接:https://www.cnblogs.com/danvic712/p/11628523.html


.NET社区新闻,深度好文,欢迎访问公众号文章汇总 http://www.csharpkit.com 

640?wx_fmt=jpeg

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/313505.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

.NET开发者必须学习.NET Core

很多的.NET开发者在接触.Net Core之前&#xff0c;对于linux系统一点也不了解&#xff0c;也未曾有过主动去学习的念头。在接触了.Net Core之后才会慢慢学习linux相关知识&#xff0c;很多同学想转Java&#xff0c;这个很扎心&#xff0c;你有很好的条件转向.NET Core为啥要转J…

Java事务管理

事务的ACID属性&#xff1a;原子性(Atomicity )、一致性( Consistency )、隔离性或独立性( Isolation)和持久性(Durabilily) ACID 特性 A&#xff08;原子性&#xff09;事务的原子操作单元&#xff0c;对数据的修改&#xff0c;要么全部执行&#xff0c;要么全部不执行&#x…

如何提高QnA maker机器人训练中文语义理解的能力

这是一个常见的问题&#xff0c;在人工智能的世界里面&#xff0c;图像理解、语言及语义理解、数据理解是三个核心领域。而关于语言及语义理解&#xff0c;又与具体的语言和文字密切相关。目前来说&#xff0c;大家都是用机器学习去训练模型&#xff0c;如果要更好的理解中文&a…

分布式数据一致性(数据多份副本一致性)

前言 分布式数据库的数据一致性管理是其最重要的内核技术之一&#xff0c;也是保证分布式数据库满足数据库最基本的ACID特性中的 “一致性”(Consistency)的保障。在分布式技术发展下&#xff0c;数据一致性的解决方法和技术也在不断的演进&#xff0c;本文就以分布式数据库作…

Bumblebee微服务网关之请求统一验证

对于微服务网关来说&#xff0c;统一请求验证是一个比较重要和常用的功能&#xff0c;通过网关验证后台服务就无须关注请求验证&#xff1b;对于多语言平台的服务而言制定验证方式和变更验证配置都是一件比较繁琐和工作量大的事情。Bumblebee提供JWT验证插件&#xff0c;只需要…

分布式事务基础

这一篇主要介绍分布式事务的基础知识&#xff0c;一些基础的算法、定理、简单应用等。下篇文章介绍互联网业界的具体实践方案。 1、CAP定理 CAP定理是由加州大学伯克利分校Eric Brewer教授提出来的&#xff0c;其核心思想是任何基于网络的数据共享&#xff0c;系统最多只能满…

支持前端、后台业务代码扩展的快速开发框架

框架采用.NetCore Vue前后端分离&#xff0c;并且支持前端、后台代码业务动态扩展&#xff0c;框架内置了一套有着20多种属性配置的代码生成器&#xff0c;可灵活配置生成的代码&#xff0c;代码生成器界面配置完成即可生成单表/主从表的增、删、改、查、导入、导出、上传、审…

保证分布式系统数据一致性的6种方案

分布式系统数据一致性的基础知识&#xff0c;传送门 1、问题的起源 在电商等业务中&#xff0c;系统一般由多个独立的服务组成&#xff0c;如何解决分布式调用时候数据的一致性&#xff1f; 具体业务场景如下&#xff0c;比如一个业务操作&#xff0c;如果同时调用服务 A、B…

15年来这8门编程语言位置十分稳定,C#从低谷开始爬升

TIOBE 编程语言排行榜 10 月份的榜单已公布&#xff0c;这期的标题比较有趣 —— “Top 8 of the TIOBE index quite stable for the last 15 years”&#xff0c;意思就是排名前 8 的编程语言在这 15 年里一直都十分稳定。有多稳定呢&#xff1f;根据 TIOBE 统计的数据&#x…

Dubbo相关

mark http://ifeve.com/dubbo-learn-book/ http://dubbo.apache.org/zh-cn/ Dubbo架构图 框架分层架构中&#xff0c;各个层次的设计要点&#xff1a; 服务接口层&#xff08;Service&#xff09;&#xff1a;该层是与实际业务逻辑相关的&#xff0c;根据服务提供方和服务消费…

同时支持EF+Dapper的混合仓储,助你快速搭建数据访问层

背景17年开始&#xff0c;公司开始向DotNet Core转型&#xff0c;面对ORM工具的选型&#xff0c;当时围绕Dapper和EF发生了激烈的讨论。项目团队更加关注快速交付&#xff0c;他们主张使用EF这种能快速开发的ORM工具&#xff1b;而在线业务团队对性能有更高的要求&#xff0c;他…

Dubbo——增强SPI的实现

一、前言 在Duboo剖析-整体架构分析中介绍了dubbo中除了Service 和 Config 层为 API外&#xff0c;其他各层均为SPI&#xff0c;为SPI意味着下面各层都是组件化可以被替换的&#xff0c;这也是dubbo比较好的一点。 二、JDK中标准SPI JDK 中的 SPI&#xff08;Service Provider…

【 .NET Core 3.0 】框架之二 || 后端项目搭建

前言至于为什么要搭建.Net Core 平台&#xff0c;这个网上的解释以及铺天盖地&#xff0c;想了想&#xff0c;还是感觉重要的一点&#xff0c;跨平台&#xff0c;嗯&#xff01;没错&#xff0c;而且比.Net 更容易搭建&#xff0c;速度也更快&#xff0c;所有的包均由Nuget提供…

怎样打造一个分布式数据库

本文来自&#xff1a;https://www.infoq.cn/article/how-to-build-a-distributed-database 文章写得很好&#xff0c;备份防丢失 在技术方面&#xff0c;我自己热衷于 Open Source&#xff0c;写了很多 Open Source 的东西&#xff0c;擅长的是 Infrastructure 领域。Infrastru…

向net core 3.0进击——Swagger的改变

前言十一小长假在不知不觉间可都没了&#xff0c;在这个小尾巴的空隙&#xff0c;把这两天鼓捣的net core 3.0升级过程记录一下&#xff0c;首先还是根据之前的顺序一个个补充进来&#xff0c;先从Swagger的变化说起&#xff08;新建工程什么的不多说了&#xff0c;就是选择的时…

Dubbo——面试问题集(1~3)

1、默认使用的是什么通信框架&#xff0c;还有别的选择吗? Dubbo默认使用netty&#xff0c;还支持mina, grizzy 配置方式&#xff1a; <dubbo:protocol name“dubbo” port“9090” server“netty” client“netty” codec“dubbo” serialization“hessian2” charset…

Dubbo——面试问题集(4~14)

4、默认使用什么序列化框架&#xff0c;你知道的还有哪些&#xff1f; 在Dubbo RPC中&#xff0c;同时支持多种序列化方式&#xff1a; dubbo序列化&#xff0c;阿里尚不成熟的java序列化实现。 hessian2序列化&#xff1a;hessian是一种跨语言的高效二进制的序列化方式&…

向net core 3.0进击——April.WebApi从2.2爬到3.0

前言在之前对Swagger的变化做了调整后&#xff0c;就开始想着要不把之前的工程升级得了&#xff0c;这样就还是个demo工程&#xff0c;来做各种测试&#xff08;当然还是因为懒&#xff09;&#xff0c;这就有了今天这个比较折腾的一步。升级之路首先&#xff0c;April.WebApi工…

共识与拜占庭将军问题

1、共识基础 人们对共识机制的研究其实由来已久&#xff0c;从上世纪70年代就开始了相关研究&#xff0c;其目的是为了解决分布式系统中的一致性问题。Fischer, Lynch 和 Patterson在1985年发表的论文中提出了可以说是最重要的分布式系统定理&#xff1a;FLP不可能定理&#x…

C#刷遍Leetcode面试题系列连载(2): No.38 - 报数

前言前文传送门&#xff1a;上篇文章中我们主要科普了刷 LeetCode 对大家的作用&#xff0c;今天咱们就正式进行 LeetCode 算法题分析。很多人都知道计算机中有种思想叫 递归&#xff0c;相应地也出现了很多算法。解决递归问题的要点有如下几个:找出递归的关系比如&#xff0c;…