在 ASP.NET Core 專案中使用 AutoMapper 進行實體對映

dotNET跨平臺發表於2019-10-08


 一、前言

  在實際專案開發過程中,我們使用到的各種 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

相關文章