在 ASP.NET Core 項目中使用 AutoMapper 進行實體映射

 1、前言

  在實際項目開發過程當中,咱們使用到的各類 ORM 組件均可以很便捷的將咱們獲取到的數據綁定到對應的 List<T> 集合中,由於咱們最終想要在頁面上展現的數據與數據庫實體類之間可能存在很大的差別,因此這裏更常見的方法是去建立一些對應於頁面數據展現的 `視圖模型` 類,經過對獲取到的數據進行二次加工,從而知足實際頁面顯示的須要。前端

  所以,如何更便捷的去實現 數據庫持久化對象 與 視圖對象 間的實體映射,避免咱們在代碼中去一次次的手工實現這一過程,就能夠下降開發的工做量,而 AutoMapper 則是能夠幫助咱們便捷的實現實體轉換這一過程的利器。因此,本章咱們就來學習如何在 ASP.NET Core 項目中經過使用 AutoMapper 去完成實體間的映射。git

  固然,若是你習慣於從視圖展示到持久化到數據庫都採用數據庫實體,那麼本篇文章對你可能不會有任何的幫助。github

  代碼倉儲:https://github.com/Lanesra712/ingos-common/tree/master/sample/aspnetcore/aspnetcore-automapper-tutorial數據庫

 2、Step by Step

  AutoMapper 是一個 OOM(Object-Object-Mapping) 組件,從名字上就能夠看出來,這一系列的組件主要是爲了幫助咱們實現實體間的相互轉換,從而避免咱們每次都採用手工編寫代碼的方式進行轉換。在沒有采用 OOM 組件以前,若是咱們須要實現相似於一份數據在不一樣客戶端顯示不一樣的字段,咱們只能以手工的、逐個屬性賦值的方式實現數據在各個客戶端數據類型間的數據傳遞,而 OOM 組件則能夠很方便的幫咱們實現這一需求。json

  一、幾個概念

  在上面咱們有提到 數據庫持久化對象 和 視圖對象 這兩個概念,其實除了這兩個對象的概念以外,還存在一個 數據傳輸對象 的概念,這裏咱們來簡單闡述下這三種對象的概念。架構

  數據庫持久化對象(Persistent Object):顧名思義,這個對象是用來將咱們的數據持久化到數據庫,通常來講,持久化對象中的字段會與數據庫中對應的 table 保持一致。app

  這裏,若是你採用了 DDD 的思想去指導設計系統架構,其實最終落地到咱們代碼中的實際上是 領域對象(Domain Object),它與 數據庫持久化對象 最顯著的差別在於 領域對象 會包含當前業務領域的各類事件,而 數據庫持久化對象 僅是包含了數據庫中對應 table 的數據字段信息。框架

  視圖對象(View Object):視圖對象 VO 是面向前端用戶頁面的,通常會包含呈現給用戶的某個頁面/組件中所包含的全部數據字段信息。函數

  數據傳輸對象(Data Transfer Object):數據傳輸對象 DTO 通常用於前端展現層與後臺服務層之間的數據傳遞,以一種媒介的形式完成 數據庫持久化對象 與 視圖對象 之間的數據傳遞。post

  這裏經過一個簡單的示意圖去解釋下這三種對象的具體使用場景,在這個示例的項目中,我省略了數據傳輸對象,將數據庫持久化對象直接轉換成頁面顯示的視圖對象。

  二、組件加載

  首先咱們須要經過 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);

  三、使用案例

  由於本來想要使用的示例項目是以前的 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,實現後的結果以下圖所示。

 3、總結

  本篇文章主要是演示下如何在 ASP.NET Core 項目中去使用 AutoMapper 來實現實體間的映射,由於以前只是在 .NET Fx 項目中有使用過這個組件,並無在 .NET Core 項目中使用,因此此次趁着國慶節假期就來嘗試如何在 .NET Core 項目中使用,整個組件使用起來實際上是很簡單的,可是使用後卻能夠給咱們在實際的項目開發中省不少的事,因此就把本身的使用方法分享出來,若是對你有些許的幫助的話,不勝榮幸~~~

相關文章
相關標籤/搜索