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

来源:https://www.cnblogs.com/danvic712/archive/2019/10/07/11628523.html
-Advertisement-
Play Games

一、前言 在實際項目開發過程中,我們使用到的各種 ORM 組件都可以很便捷的將我們獲取到的數據綁定到對應的 List<T> 集合中,因為我們最終想要在頁面上展示的數據與資料庫實體類之間可能存在很大的差異,所以這裡更常見的方法是去創建一些對應於頁面數據展示的 `視圖模型` 類,通過對獲取到的數據進行二 ...


 一、前言

  在實際項目開發過程中,我們使用到的各種 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 一般用於前端展示層與後臺服務層之間的數據傳遞,以一種媒介的形式完成 資料庫持久化對象 與 視圖對象 之間的數據傳遞。

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

  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 項目中使用,整個組件使用起來其實是很簡單的,但是使用後卻可以給我們在實際的項目開發中省很多的事,所以就把自己的使用方法分享出來,如果對你有些許的幫助的話,不勝榮幸~~~


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 非常興奮加入博客園大家庭 ...
  • 經典例題 if嵌套 1.用戶輸入賬號2.用戶輸入密碼3.判斷用戶的賬號是不是alex4.如果賬號是alex在繼續判斷密碼是不是alexdsb5.賬號和密碼都正確提示用戶alex就是一個dsb6.如果賬號正確密碼錯誤提示密碼錯誤7.如果賬號錯誤提示賬號錯誤 user = input("請輸入賬號:") ...
  • 本文主要實現無動態刷新查詢後臺數據功能,主要用到ajax+ashx+sqlserver進行交互. 首先需要引用Jquery: html腳本: 前臺通過一個事件來調用ashx: 後臺來接收前臺傳過來的值,對其進行操作: SerializerHelper類的定義: 如果向後臺傳入多個參數在data裡面用 ...
  • 前景:要操作的數據表必須添加主鍵(方式:進入資料庫-->數據表名-->設計-->列名右鍵-->設置主鍵) 可在伺服器資源管理器中查看是否設置了主鍵(主鍵會有一把鑰匙的圖樣) 1)、項目名右鍵-->新建項-->ADO.NET數據模型 選擇第一個“來自資料庫的EF設計器”就行 如果是第一次連接,點擊新建 ...
  • .Net Core應用發佈到IIS主要是如下的三個步驟: (1)在Windows Server上安裝 .Net Core Hosting Bundle (2)在IIS管理器中創建IIS站點 (3)部署ASP.NET Core應用 一.安裝 .Net Core Hosting Bundle 打開鏈接 ...
  • 一般情況下,一個 .NET 程式集載入到程式中以後,它的類型信息以及原生代碼等數據會一直保留在記憶體中,.NET 運行時無法回收它們,如果我們要實現插件熱載入 (例如 Razor 或 Aspx 模版的熱更新) 則會造成記憶體泄漏。在以往,我們可以使用 .NET Framework 的 AppDomain ...
  • 很多的.NET開發者在接觸.Net Core之前,對於linux系統一點也不瞭解,也未曾有過主動去學習的念頭。在接觸了.Net Core之後才會慢慢學習linux相關知識,很多同學想轉Java,這個很扎心,你有很好的條件轉向.NET Core為啥要轉Java,據說目前市場上Java多如牛毛,兩年以內 ...
  • 乍一接觸"匿名方法中的捕獲變數"這一術語可能會優點蒙,那什麼是"匿名方法中的捕獲變數"呢?在章節未開始之前,我們先定義一個委托:public delegate void MethodInvoke(); 1、閉包和不同類型的變數: 首先,大家應該都知道"閉包",它的概念是:一個函數除了能通過提供給它的 ...
一周排行
    -Advertisement-
    Play Games
  • 概述:在C#中,++i和i++都是自增運算符,其中++i先增加值再返回,而i++先返回值再增加。應用場景根據需求選擇,首碼適合先增後用,尾碼適合先用後增。詳細示例提供清晰的代碼演示這兩者的操作時機和實際應用。 在C#中,++i 和 i++ 都是自增運算符,但它們在操作上有細微的差異,主要體現在操作的 ...
  • 上次發佈了:Taurus.MVC 性能壓力測試(ap 壓測 和 linux 下wrk 壓測):.NET Core 版本,今天計劃準備壓測一下 .NET 版本,來測試並記錄一下 Taurus.MVC 框架在 .NET 版本的性能,以便後續持續優化改進。 為了方便對比,本文章的電腦環境和測試思路,儘量和... ...
  • .NET WebAPI作為一種構建RESTful服務的強大工具,為開發者提供了便捷的方式來定義、處理HTTP請求並返迴響應。在設計API介面時,正確地接收和解析客戶端發送的數據至關重要。.NET WebAPI提供了一系列特性,如[FromRoute]、[FromQuery]和[FromBody],用 ...
  • 原因:我之所以想做這個項目,是因為在之前查找關於C#/WPF相關資料時,我發現講解圖像濾鏡的資源非常稀缺。此外,我註意到許多現有的開源庫主要基於CPU進行圖像渲染。這種方式在處理大量圖像時,會導致CPU的渲染負擔過重。因此,我將在下文中介紹如何通過GPU渲染來有效實現圖像的各種濾鏡效果。 生成的效果 ...
  • 引言 上一章我們介紹了在xUnit單元測試中用xUnit.DependencyInject來使用依賴註入,上一章我們的Sample.Repository倉儲層有一個批量註入的介面沒有做單元測試,今天用這個示例來演示一下如何用Bogus創建模擬數據 ,和 EFCore 的種子數據生成 Bogus 的優 ...
  • 一、前言 在自己的項目中,涉及到實時心率曲線的繪製,項目上的曲線繪製,一般很難找到能直接用的第三方庫,而且有些還是定製化的功能,所以還是自己繪製比較方便。很多人一聽到自己畫就害怕,感覺很難,今天就分享一個完整的實時心率數據繪製心率曲線圖的例子;之前的博客也分享給DrawingVisual繪製曲線的方 ...
  • 如果你在自定義的 Main 方法中直接使用 App 類並啟動應用程式,但發現 App.xaml 中定義的資源沒有被正確載入,那麼問題可能在於如何正確配置 App.xaml 與你的 App 類的交互。 確保 App.xaml 文件中的 x:Class 屬性正確指向你的 App 類。這樣,當你創建 Ap ...
  • 一:背景 1. 講故事 上個月有個朋友在微信上找到我,說他們的軟體在客戶那邊隔幾天就要崩潰一次,一直都沒有找到原因,讓我幫忙看下怎麼回事,確實工控類的軟體環境複雜難搞,朋友手上有一個崩潰的dump,剛好丟給我來分析一下。 二:WinDbg分析 1. 程式為什麼會崩潰 windbg 有一個厲害之處在於 ...
  • 前言 .NET生態中有許多依賴註入容器。在大多數情況下,微軟提供的內置容器在易用性和性能方面都非常優秀。外加ASP.NET Core預設使用內置容器,使用很方便。 但是筆者在使用中一直有一個頭疼的問題:服務工廠無法提供請求的服務類型相關的信息。這在一般情況下並沒有影響,但是內置容器支持註冊開放泛型服 ...
  • 一、前言 在項目開發過程中,DataGrid是經常使用到的一個數據展示控制項,而通常表格的最後一列是作為操作列存在,比如會有編輯、刪除等功能按鈕。但WPF的原始DataGrid中,預設只支持固定左側列,這跟大家習慣性操作列放最後不符,今天就來介紹一種簡單的方式實現固定右側列。(這裡的實現方式參考的大佬 ...