Alan Tsai 的學習筆記


學而不思則罔,思而不學則殆,不思不學則“網貸” 為現任微軟最有價值專家 (MVP)、微軟認證講師 (MCT) 、Blogger、Youtuber:記錄軟體開發的點點滴滴 著重於微軟技術、C#、ASP .NET、Azure、DevOps、Docker、AI、Chatbot、Data Science

[iThome 第七屆鐵人賽 07] AutoMapper 介紹 - 簡單化Entity和ViewModel之間的轉換

在上一篇介紹完ViewModel的好處之後,留下的問題是,ViewModel雖然有帶來好處,但是ViewModel和實際Entity之間的對應其實是很麻煩的一件事情,那麼我們如何能夠簡化對應的邏輯呢?

這時候就是AutoMapper這個套件入場的時候。

AutoMapper介紹

AutoMapperimage

AutoMapper的目的就是要解決無聊的左邊資料倒到右邊。我們舉一個例子,如果是在早期的Asp .Net Webform,當一個Form進來的時候,我們常常會需要:

// psuedo 程式碼

string name = Request.Form["name"];
string age = Request.Form["age"];
.....
這些其實很無聊但是又不得不做。在Mvc裡面Model Binding解決了這個問題。

但是如果用ViewModel,還是有這個問題,因此就有人開發了AutoMapper。


AutoMapper簡單來說,使用步奏就是:



  1. 定義好兩個Class之間轉換的邏輯
  2. 把object透過AutoMapper轉換成為另外一個形態的object

測試情景


在介紹AutoMapper之前,我們先設定好我們的測試情景。假設我們有一個DB,裡面一個Table叫做Post,代表著一個部落格網站裡面所擁有的文章。Entity可能如下:

public partial class Post
{
    public int Id { get; set; }
    public string Title { get; set; }
    public string PostContent { get; set; }
    public System.DateTime CreateDateTime { get; set; }
    public Nullable<System.DateTime> LastModifyDateTime { get; set; }
}

那用這個Entity,透過Mvc的Scaffolding,我們建立出基本的CRUD頁面。那Scaffolding出來的CRUD,Entity會是預設的ViewModel。透過上一篇我們知道使用Entity做ViewModel的壞處是什麼,因此我們會開始針對CRUD建立對應的ViewModel。


Index頁面的ViewModel


沒有AutoMapper的做法


假設我說,我們的Index頁面不要顯示CreateDateTimeLastModifyDateTime。我們當然可以只改View的HtmlHelper,不要顯示這兩個Property,不過等一下我們就知道為什麼用ViewModel更好。


因此,我建立了一個如下的ViewModel:

public class IndexViewModel
{
    public int Id { get; set; }
    public string Title { get; set; }
    public string PostContent { get; set; }
}

在沒有時候AutoMapper的情況下,我們會需要做:

List<IndexViewModel> viewModel = new List<IndexViewModel>();

foreach (var item in db.Post.ToList())
{
    viewModel.Add(new IndexViewModel()
    {
        Id = item.Id,
        PostContent = item.PostContent,
        Title = item.Title
    });
}

想想,我們才3個property而已就佔了這麼多行數的程式碼,如果有20個property不就很恐怖。而且,明明兩邊的Property都一樣,他不能夠自己對應嗎?


用上AutoMapper


如果用上AutoMapper,程式碼變成:

// 定義Post是來源的Class而IndexViewModel是最後結果
Mapper.CreateMap<Post, IndexViewModel>();

// 把List<Post>轉成List<IndexViewModel>
var viewModel2 = Mapper.Map<List<IndexViewModel>>(db.Post.ToList());

用了AutoMapper總共有2個好處:



  1. 程式碼變少了:本來要11行,現在只要2行
  2. 程式碼的可讀性提高:本來還會跑迴圈,如果property參數多了,看起來不是那麼直覺。但是用AutoMapper,非常直覺知道是在轉換Model形態

可能你會在想,為什麼我們什麼都沒有設定,AutoMapper就知道對應欄位是什麼?其實因為AutoMapper會自動把一樣的Property名字作為一對,以我們的例子,Property都一樣,因此我不需要做額外設定。


需求變更


假設,今天我們Index頁面的需求變了,變成在Index頁面的每一筆Post需要顯示最後一次修改時間距離建立時間過了幾天,如果沒有最後一次修改時間,就用今天日期,這個時候,我們就需要手動設定Property的值要如何產生。


首先我們在IndexViewModel增加一個Property:

public double HowManyDayPass { get; set; }

然後AutoMapper的轉換邏輯改一下:

...
Mapper.CreateMap<Post, IndexViewModel>()
    .ForMember(member => member.HowManyDayPass, 
    opt => opt.MapFrom(x => x.LastModifyDateTime == null ? 
        (DateTime.Now - x.CreateDateTime).TotalDays : 
        (x.LastModifyDateTime.Value - x.CreateDateTime).TotalDays));
....

應該很好懂,設定member.HowManyDayPass這個property的值要從:如果沒有最後修改時間,就用今天日期減掉建立日期。不然就用 最後修改時間減掉建立時間


Queryable Extension - 減少Sql Select的欄位


AutoMapper其實有提供一個IQueryable的Extension方法,讓我們EF在對DB下Sql的時候,只下我們需要的部分。我們直接看例子:

//需要先加Using
using AutoMapper.QueryableExtensions;
...

var projectIQueryable = (db.Post.Project().To<IndexViewModel>()).ToList(); // AutoMapper QuerableExtension

var normalIQuerable = db.Post.AsQueryable().ToList();

...
image
用了AutoMapper產生的Sql(左邊)和沒用的產生Sql(右邊)

有這樣的效果是因為EF用了LazyLoading。


Edit頁面的ViewModel


這邊我快速介紹一下修改頁面的ViewModel。


以我們的例子,只允許修改TitlePostContent而已,同時,LastModifyDateTime應該要自動使用系統時間。


這個時候我們的EditViewModel就會是:

public class EditViewModel
{
    public int Id { get; set; }
    public string Title { get; set; }
    public string PostContent { get; set; }
}

AutoMapper的設定:

[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit(EditViewModel post)
{
    if (ModelState.IsValid)
    {
        // 建立Mapping邏輯,並且LastModifyDateTime使用系統時間
        Mapper.CreateMap<EditViewModel, Post>().
            ForMember(member => member.LastModifyDateTime, 
            opt => opt.UseValue(DateTime.Now));

        Post postEntity = db.Post.Find(post.Id);

        // 只更新ViewModel的部分到Entity
        Mapper.Map(post, postEntity);
            
        db.Entry(postEntity).State = EntityState.Modified;
        db.SaveChanges();
        return RedirectToAction("Index");
    }
    return View(post);
}

結語


到目前為止我們介紹了AutoMapper的基本概念,和基本的使用。相信在整體使用上面來說,比自己手動轉換來的方便。但是,假設以我們目前介紹的方式去做,AutoMapper還是有點不好用。


其實最主要的問題是在:設定對應邏輯的地方。雖然說AutoMapper簡化了很多設定,可是還是要設定啊,這些設定到底要放在那裡?想象一下,假設我們每一個View有個 ViewModel,一個功能至少有CRUD,很快我們就有一堆ViewModel,這些設定就變的不好維護。雖然說AutoMapper簡化了很多設定,可是還是要設定啊,這些設定到底要放在那裡?想象一下,假設我們每一個View有個 ViewModel,一個功能至少有CRUD (4個ViewModel),很快我們就有一堆ViewModel,這些設定就變的不好維護


因此在下一篇,我們會建立一些功能,讓我們的在開發上面使用起來更方便和好維護。


如果文章對您有幫助,就請我喝杯飲料吧
街口支付QR Code
街口支付QR Code
台灣 Pay QR Code
台灣 Pay QR Code
Line Pay 一卡通 QR Code
Line Pay 一卡通 QR Code
街口支付QR Code
支付寶QR Code
街口支付QR Code
微信支付QR Code
comments powered by Disqus