2014年3月1日 星期六

AutoMapper 兩個物件對映到一個類別

之前的兩篇文章或是一般的應用都是取得某個類別的一個物件後再去對映到目標類別物件,

使用 AutoMapper 處理類別之間的對映轉換

AutoMapper 的設定 (Configuration)

如果要將兩個以上的物件對映到目標類別,應該怎麼做呢?

 


使用環境:Windows 8.1, Visual Studio 2013, MS SQL Server Express 2012, ASP.NET MVC 5

範例資料庫:Northwind

 

在 Visual Studo 2013 裡建立一個 ASP.NET MVC 5 專案,然後使用 Northwind 建立 ADO.NET 實體資料模型,然後專案使用 Nuget 加入 AutoMapper(目前版本為 3.1.1)。

image

在專案裡定義一個類別「CustomerOrder」

image

因為是用來示範說明,所以先不管為什麼這個類別要這樣定義以及是用來做什麼用的,總之這個類別裝的就是 CustomerID, CustomerName 以及 OrderID,那因為一個顧客會有很多張訂單,所以這邊就取顧客的第一張訂單的 OrderID。

接著我們在 Method 裡建立類別對映的設定,

image

我想上面的對映設定是相當簡單的,從兩個類別裡分別將指定的屬性對映到目標類別中,所以分別建立的 Customer 與 Order 對映到 CustomerOrder 的設定。

接著就是從資料庫取得資料之後,再透過 AutoMapper 完成資料的對映轉換,如果是按照平常我們使用 AutoMapper 的操作,程式就會是這樣寫,

image

上面的程式看起來是理所當然,不過在執行後會發現到不對勁……

在完成第一個 Customer 資料對映到 CustomerOrder 的動作後觀察結果,可以看到都有將資料對映到目標物件裡,

image

而在完成第二個 Order 資料對映到 CustomerOrder 後去觀察結果,卻看到原本有對映到的 CustomerName 卻變成 Null,

image

這是因為 Order 類別裡也有個 CustomerID 的屬性,

image

我們做資料對映時所使用的方法不對,使得原本已經完成 Customer 資料對映的操作再進行 Order 資料對映時就不算數了,第二次資料對映是從 Order 類別裡取得 OrderID 與 CustomerID 的資料來做對映,

image

因為使用 Map<TSource, TDestination>(TSource source) 方法是每次的資料對映結果都會是一個新的目標類別物件,所以之前對映的就會不算數了,第二次所對映的結果又會是一個新的目標類別物件。

SNAGHTMLab1dc9

我們應該使用的是另一個對映方法,如下:

SNAGHTMLae6f74

將之前已經對映的結果帶入 Map 方法的第二個參數,這樣第二次的資料對映就會使用已經存在的目標類別物件了。

 

所以將原本的程式修改如下:

image

執行結果:

image

 

在執行多個來源物件對映到目標類別物件,可以建立多個資料對映設定,然後在執行資料對映的時候要注意所使用的 Map 方法。

 

 

以上

9 則留言:

  1. 我想請問一下,目前照你的做法,但是第一個Customers如果是空值的話,會出錯的,我的做法是在這之前先針對第一個做count,但就會多損耗一點效能,能幫忙解一下比較好的寫法嗎?

    回覆刪除
    回覆
    1. 你應該要繼續看下去.... 後面還有幾篇文章喔

      刪除
  2. 請問一下哦,如果是像一對多的JOIN這種語法的怎麼對映呢??例如像下面這種SQL
    select a.*,b.醫師姓名 from OwnExpenseD a left join Doctor b on b.醫師代號=a.DoctorId
    在我這邊是寫成這樣子,但是第一個對映的都會變成NULL值
    我這跟您有點不同的,只差在我是多筆的狀況...........
    int CurrentData = Page == 0 ? Page = 0 : (Page-1) * 10;
    Mapper.CreateMap()
    .ForMember(x => x.CreateDate, y => y.MapFrom(s => convertDate.ConvertTw(s.CreateDate)))
    .ForMember(x => x.PaymentDate, y => y.MapFrom(s => convertDate.ConvertTw(s.PaymentDate)));

    Mapper.CreateMap()
    .ForMember(x => x.DoctorName, y => y.MapFrom(s => s.DoctorName));

    var ownExpenseD = await db1.OwnExpenseD.Where(x => x.OwnId == OwnId)
    .OrderByDescending(x => x.Id).Skip(CurrentData).Take(10).ToListAsync();

    IEnumerable doctor = from a in ownExpenseD
    join b in db1.Doctor
    on a.DoctorId equals b.醫師代號
    select new doctorViewModel()
    {
    DoctorName = b.醫師姓名
    };

    IEnumerable result;
    result = Mapper.Map, IEnumerable>(ownExpenseD);
    result = Mapper.Map, IEnumerable>(doctor,result);
    return result;

    回覆刪除
    回覆
    1. Hello, 你好, 我重新回覆你的問題
      如果不使用 AutoMapper 的話,你的操作會是正常的嗎?
      另外你所貼上來的 Code 包含角括號,所以部份程式碼就會被系統給移除,
      所以我並不清楚你的程式內容,所以我無法提供進一步的回答。

      刪除
    2. 您好,我重新貼一下我目前沒使用automapper的CODE給您看,至於 convertDate.ConvertTw(s.CreateDate)))這個,只是把西元年轉成民國年格式做輸出,其實現在貼的CODE沒有什麼問題,只是整個畫面只會呈現出醫師姓名,原本OwnExpenseD 的欄位全部都變成空白的,只會顯示最後result = Mapper.Map, IEnumerable>(doctor,result); 的一個醫師姓名而已

      目前我的CODE是寫成下面這樣子,這樣子就整個畫面都會變成是有值的,但如果一旦欄位以後越來越多的時候,想必這樣寫的話就會非常的麻煩,而我目前只是把原本OwnExpenseD這個TABLE定義成一個VIEWMODEL,然後多加一個DoctorName而已....

      跟您目前這篇文章的說明比較起來,只是原本您都是單筆的,我這邊是多筆的狀態而已...........

      public async Task> GetAllOwnExpenseDModel(string OwnId, int Page)
      {
      int CurrentData = Page == 0 ? Page = 0 : (Page-1) * 10;
      var ownExpenseD = await db1.OwnExpenseD.Where(x => x.OwnId == OwnId)
      .OrderByDescending(x => x.Id).Skip(CurrentData).Take(10).ToListAsync();
      var result = (from a in ownExpenseD
      join b in db1.Doctor
      on a.DoctorId equals b.醫師代號
      select new OwnExpenseDModel()
      {
      OwnId=a.OwnId,
      Bill=a.Bill,
      Cash=a.Cash,
      CreateDate= convertDate.ConvertTw(a.CreateDate),
      PaymentDate=convertDate.ConvertTw(a.PaymentDate),
      CreditCard=a.CreditCard,
      DateOption=a.DateOption,
      Explanation=a.Explanation,
      GetRecepit=a.GetRecepit,
      Installments=a.Installments,
      Id=a.Id,
      Remittance=a.Remittance,
      DoctorId=a.DoctorId,
      DoctorName = b.醫師姓名,
      Receipt=a.Receipt
      }).ToList();
      return result;
      }

      刪除
    3. 不好意思,後來我發現我原本貼的程式碼好像有些部份被系統吃掉了,我附上貼圖包括呈現畫面的結果給您看,這樣子會比較清楚,分別有四張圖。
      第一張圖是目前的程式碼
      http://i.imgur.com/Zo2bKXl.png

      第二張圖則是ownExpenseD的資料集
      http://i.imgur.com/gvV9sc8.png

      第三張圖則是doctorViewModel的資料集
      http://i.imgur.com/q4i4hiE.png

      第四張圖則是結合後的結果,只剩doctorViewModel有資料,前面那個被清空了
      http://imgur.com/Zo2bKXl,gvV9sc8,q4i4hiE,fU6T1xn#3

      刪除
    4. 其實你可以透過左邊中間的那個「詢問與建議」將檔案與問題給我,可以附加檔案而且不會公開出來。

      刪除
  3. 剛試了一下~~~若是採用兩個物件來源都是LIST的情況下對映到另外一個LIST的情況下
    好像都不會成功耶

    回覆刪除
    回覆
    1. Hello
      我這邊經常都是在使用 AutoMapper 處理多個 Collection 對映到另一個 Collection 的操作,
      所以這要看你的設定是怎麼寫的喔,另外也要看你是怎麼作對映處理。

      刪除

提醒

千萬不要使用 Google Talk (Hangouts) 或 Facebook 及時通訊與我聯繫、提問,因為會掉訊息甚至我是過了好幾天之後才發現到你曾經傳給我訊息過,請多多使用「詢問與建議」(在左邊,就在左邊),另外比較深入的問題討論,或是有牽涉到你實作程式碼的內容,不適合在留言板裡留言討論,請務必使用「詢問與建議」功能(可以夾帶檔案),謝謝。