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

前言

        在实际项目开发过程当中,咱们使用到的各类 ORM 组件均可以很便捷的将咱们获取到的数据绑定到对应的 List<T> 集合中,由于咱们最终想要在页面上展现的数据与数据库实体类之间可能存在很大的差别,因此这里更常见的方法是去建立一些对应于页面数据展现的 视图模型 类,经过对获取到的数据进行二次加工,从而知足实际页面显示的须要。前端

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

        固然,若是你习惯于从视图展示到持久化到数据库都采用数据库实体,那么本篇文章对你可能不会有任何的帮助。程序员

        仓储地址:github.com/Lanesra712/…github

Step by Step

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

        1、几个概念数据库

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

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

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

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

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

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

数据模型流转示意

        2、组件加载

        首先咱们须要经过 Nuget 将 AutoMapper 加载到项目中,由于这个示例项目只包含一个 MVC 的项目,并无多余的分层,因此这里须要将两个使用到的 dll 都添加到这个 MVC 项目中。

Install-Package AutoMapper
Install-Package AutoMapper.Extensions.Microsoft.DependencyInjection
复制代码

组件加载

        这里我添加了 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 对象,项目中每一个文件夹的做用见下图所示。

示例代码说明

        这里的示例项目是演示当咱们从数据库获取到须要的数据后,如何完成从 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>();
    }
}
复制代码

配置映射关系

        经过泛型的 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,实现后的结果以下图所示。

示意图

总结

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

占坑

        做者:墨墨墨墨小宇
        我的简介:96年生人,出生于安徽某四线城市,毕业于Top 10000000 院校。.NET程序员,枪手死忠,喵星人。于2016年12月开始.NET程序员生涯,微软.NET技术的坚决坚持者,立志成为云养猫的少年中面向谷歌编程最厉害的.NET程序员。
        我的博客:yuiter.com
        博客园博客:www.cnblogs.com/danvic712

相关文章
相关标签/搜索