1. 程式人生 > >C#進階系列——DDD領域驅動設計初探(五):AutoMapper使用

C#進階系列——DDD領域驅動設計初探(五):AutoMapper使用

前言:前篇搭建了下WCF的程式碼,就提到了DTO的概念,對於為什麼要有這麼一個DTO的物件,上章可能對於這點不太詳盡,在此不厭其煩再來提提它的作用:

  • 從安全上面考慮,領域Model都帶有領域業務,讓Client端引用Domain Model就意味著Client端可以繞過應用層直接完成業務邏輯的呼叫,這樣是一種不安全的機制。
  • 從物件傳遞效率上面考慮,領域Model帶有業務,而這些業務一般對於UI層是沒有意義的,所以帶有業務的model傳遞起來會加重網路負擔。
  • 網上還說了DTOmodel最大的意義在於跨平臺,Domain Model都是與特定的語言的資料型別有關,而這些資料型別是不能跨平臺的,比如Java的型別就不能被C#使用。但在分散式模式下,Client端與Server端的平臺不同是很正常的,如果Service直接返回Domain Model,Client端根本無法解析,這就要求Service返回的結果必須是標準的格式位元組流。讓Domain Model只使用簡單型別(字元和數值)?讓資料型別約束Domain Model顯然不是一個好想法,所以DTO似乎是必不可少的了。

既然我們要使用DTO,那麼有一件事我們就非做不可了,我們從領域層得到的是領域Model,如何把領域Model轉換成只帶有資料屬性的DTO傳遞到前臺呢?又或者我們從前臺提交一個DTO物件,如何將DTO轉換成領域Model而提交到後臺呢?這個時候就需要我們的物件對映工具,目前市面上物件對映工具較多,但博主最熟悉的還是Automapper,這章就來分享下Automapper的使用。

DDD領域驅動設計初探系列文章:

一、AutoMapper

Automapper是一個object-object mapping(物件對映)工具,一般主要用於兩個物件之間資料對映和交換。當然你也可以自己通過反射去寫物件的對映,對於簡單的兩個屬性間的資料轉換,肯定沒什麼問題。但是如果遇到某些複雜的資料轉換,比如指定某一個物件的某個屬性對映到另一個物件的某一個屬性,這種情況如果我們自己手動對映,恐怕就有點麻煩了吧。既然我們有現成的工具,為什麼不用呢?

二、AutoMapper引用到專案中

向專案中新增AutoMapper的引用有兩種方式:

1、Nuget方式

在需要使用AutoMapper的專案檔案上面右鍵→管理Nuget程式包,開啟Nuget介面,搜尋Automapper,然後安裝第一個即可。如下圖:

2、程式包管理控制檯方式

點選Visual Studio的工具選單→程式包管理控制檯,然後選擇需要安裝Automapper的專案(下圖中的預設專案),最後在控制檯裡面輸入命令“Install-Package AutoMapper”命令即可按照Automapper包:

 三、AutoMapper使用程式碼示例

1、最簡單的物件對映

AutoMapper使用起來還是比較簡單的,最簡單的用法你只需要兩句話:

var oMenu = new TB_MENU() { MENU_NAME="許可權管理", MENU_LEVEL="1" };
Mapper.CreateMap
<TB_MENU, DTO_TB_MENU>(); var oDto = Mapper.Map<DTO_TB_MENU>(oMenu);

首先建立對映,然後傳入需要對映的物件執行對映。相信在專案中使用過AutoMapper的原因肯定也寫過類似這樣的AutoMapperHelper 

    /// <summary>
    /// AutoMapper幫助類
    /// </summary>
    public static class AutoMapperHelper
    {
        /// <summary>
        ///  單個物件對映
        /// </summary>
        public static T MapTo<T>(this object obj)
        {
            if (obj == null) return default(T);
            Mapper.CreateMap(obj.GetType(), typeof(T));
            return Mapper.Map<T>(obj);
        }

        /// <summary>
        /// 集合列表型別對映
        /// </summary>
        public static List<TDestination> MapToList<TSource, TDestination>(this IEnumerable<TSource> source)
        {
            Mapper.CreateMap<TSource, TDestination>();
            return Mapper.Map<List<TDestination>>(source);
        }
    }

當然,這是最簡單的用法,稍微複雜點的用法我們在後面慢慢介紹。

2、指定欄位的物件對映

前面說了,對於指定某一個物件的某個屬性對映到另一個物件的某一個屬性,這種場景,我們先來看看下面程式碼:

   public partial class TB_USERS : BaseEntity
    {
        public string USER_ID { get; set; }
        public string USER_NAME { get; set; }
        public string USER_PASSWORD { get; set; }
        public string FULLNAME { get; set; }
        public string DEPARTMENT_ID { get; set; }

      public virtual TB_DEPARTMENT TB_DEPARTMENT { get; set; }
     //...後面肯定還有其他領域行為
    }
   public partial class TB_DEPARTMENT : BaseEntity
    {
        public string DEPARTMENT_ID { get; set; }
        public string NAME { get; set; }
    }

 領域層有這兩個實體model,然後我們需要得到下面的DTO_TB_USERS這一個物件

   public class DTO_TB_USERS
    {
        [DataMember]
        public string USER_ID { get; set; }
        
        [DataMember]
        public string USER_NAME { get; set; }
        
        [DataMember]
        public string USER_PASSWORD { get; set; }
        
        [DataMember]
        public string FULLNAME { get; set; }
        
        [DataMember]
        public string DEPARTMENT_ID { get; set; }

     [DataMember]
     public string DEPARTMENT_NAME { get; set; }
}

 這個時候DTO_TB_USERS這個物件的屬性分佈在其他兩個領域實體裡面,我們看看AutoMapper如何解決:

var oDomainUser = userRepository.Entities.FirstOrDefault();
var map = Mapper.CreateMap<TB_USERS, DTO_TB_USERS>();
map.ForMember(d => d.DEPARTMENT_NAME, opt => opt.MapFrom(x => x.TB_DEPARTMENT.NAME));
var oDto = Mapper.Map<TB_USERS, DTO_TB_USERS>(oDomainUser);  

通過上面的程式碼,ForMember()方法會指定哪個欄位轉換為哪個欄位,這樣就完美的將物件的層級結構由二級變成了一級(即將TB_USERS下面TB_DEPARTMENT物件的NAME值轉換成了DTO_TB_USERS的DEPARTMENT_NAME值)。除此之外,Automapper裡面還可以通過ForMember幫我們做其他很多我們想不到的事情,比如可以設定某個屬性值保留初始值,只需要通過

map.ForMember(d => d.DEPARTMENT_NAME, opt => opt.Ignore());

這一句就幫我們搞定。 

3、傳遞lamada的表示式對映

還記得我們在倉儲裡面封裝了傳遞lamada表示式的查詢方法麼?試想,如果我們在Web層裡面也希望傳遞lamada表示式去後臺查詢,那麼這個時候就有點問題了,因為我們Web裡面只能訪問DTO的Model,所以只能傳入DTO Model的lamada,而我們倉儲裡面需要傳入的是領域Model的lamada,那麼問題就來了,這兩個lamada表示式之間必須存在一個轉換關係,試想,這些東西如果讓我們手動去處理,還是有難度的吧!還好,我們神奇的Automapper替我們想到了。它能夠幫我們將DTO的lamada轉換成領域Model的lamada,來看看程式碼吧:

     [Import]
     public IUserRepository userRepository { get; set; }

     public virtual IList<DTO> Find(Expression<Func<DTO, bool>> selector)
        {
            //得到從Web傳過來和DTOModel相關的lamaba表示式的委託
            Func<DTO, bool> match = selector.Compile();
            //建立對映Expression的委託
            Func<T, DTO> mapper = AutoMapper.QueryableExtensions.Extensions.CreateMapExpression<T, DTO>(Mapper.Engine).Compile();
            //得到領域Model相關的lamada
            Expression<Func<T, bool>> lamada = ef_t => match(mapper(ef_t));
            List<T> list = userRepository.Find(lamada).ToList();
            return Mapper.Map<List<T>, List<DTO>>(list);
        }

上面方法完美實現了兩種lamada之間的轉換,但根據博主的使用經歷,這種轉換對屬性的型別有很嚴格的要求,必須保證領域model和DTO的Model同一個屬性的型別完全相同,否則容易報異常。使用的時候需要注意。實際使用的方法:

public List<DtoModel> GetDtoByLamada<DtoModel,DomainModel>(IRepository<DomainModel> oRepository,  Expression<Func<DtoModel, bool>> selector = null)
            where DomainModel : AggregateRoot
            where DtoModel : DTO_BASEMODEL
        {
            if (selector == null)
            {
                var lstDomainModel = oRepository.Entities.ToList();
                return Mapper.Map<List<DomainModel>, List<DtoModel>>(lstDomainModel);
            }
            //得到從Web傳過來和DTOModel相關的lamaba表示式的委託
            Mapper.CreateMap<DtoModel, DomainModel>();
            Mapper.CreateMap<DomainModel, DtoModel>();
            Func<DtoModel, bool> match = selector.Compile();
            //建立對映Expression的委託
            Func<DomainModel, DtoModel> mapper = AutoMapper.QueryableExtensions.Extensions.CreateMapExpression<DomainModel, DtoModel>(Mapper.Engine).Compile();
            //得到領域Model相關的lamada
            Expression<Func<DomainModel, bool>> lamada = ef_t => match(mapper(ef_t));
            List<DomainModel> list = oRepository.Find(lamada).ToList();
            return Mapper.Map<List<DomainModel>, List<DtoModel>>(list);
        }

呼叫

public class PowerManageWCFService :BaseService, IPowerManageWCFService
    {
        #region Fields
        [Import]
        private IUserRepository userRepository { get; set; }

        [Import]
        private IDepartmentRepository departmentRepository { get; set; }

        [Import]
        private IRoleRepository roleRepository { get; set; }

        [Import]
        private IMenuRepository menuRepository { get; set; } 
        #endregion

        #region Constust
        public PowerManageWCFService()
        {
            //註冊MEF
            Regisgter.regisgter().ComposeParts(this);
        }
        #endregion

        #region WCF服務介面實現
        public List<DTO_TB_USERS> GetUsers(Expression<Func<DTO_TB_USERS, bool>> selector)
        {
            return base.GetDtoByLamada<DTO_TB_USERS, TB_USERS>(userRepository, selector);
        }

        public List<DTO_TB_DEPARTMENT> GetDepartments(Expression<Func<DTO_TB_DEPARTMENT, bool>> selector)
        {
            return base.GetDtoByLamada<DTO_TB_DEPARTMENT, TB_DEPARTMENT>(departmentRepository, selector);
        }

        public List<DTO_TB_ROLE> GetRoles(Expression<Func<DTO_TB_ROLE, bool>> selector)
        {
            return base.GetDtoByLamada<DTO_TB_ROLE, TB_ROLE>(roleRepository, selector);
        }

        public List<DTO_TB_MENU> GetMenus(Expression<Func<DTO_TB_MENU, bool>> selector)
        {
            return base.GetDtoByLamada<DTO_TB_MENU, TB_MENU>(menuRepository, selector);
        } 
        #endregion
}

4、Automapper的其他應用

除了上面介紹的Automapper的幾個簡單使用,其他還有其他的一些用法。

網上很多介紹DataReader物件和實體類之間的對映:

using (IDataReader reader = db.ExecuteReader(command))
{
    if (reader.Read())
    {
        return AutoMapper.Mapper.DynamicMap<Product>(reader);
    }
}

 至此,AutoMapper的常見用法基本分享完了,至於更高階的用法,有興趣可以看看蟋蟀兄的【AutoMapper官方文件】DTO與Domin Model相互轉換(上)。雖然很多高階用法在實際專案中很難用上,但多瞭解一點似乎也並沒有壞處。

相關推薦

C#系列——DDD領域驅動設計初探AutoMapper使用

前言:前篇搭建了下WCF的程式碼,就提到了DTO的概念,對於為什麼要有這麼一個DTO的物件,上章可能對於這點不太詳盡,在此不厭其煩再來提提它的作用: 從安全上面考慮,領域Model都帶有領域業務,讓Client端引用Domain Model就意味著Client端可以繞過應用層直接完成業務邏輯的呼叫,這樣

C#系列——DDD領域驅動設計初探領域服務

前言:之前一直在搭建專案架構的程式碼,有點偏離我們的主題(DDD)了,這篇我們繼續來聊聊DDD裡面另一個比較重要的知識點:領域服務。關於領域服務的使用,書中也介紹得比較晦澀,在此就根據博主自己的理解談談這個知識點的使用。 DDD領域驅動設計初探系列文章: 一、領域服務的引入 在《領域驅動設計:軟體核

C#系列——DDD領域驅動設計初探WCF搭建

前言:前面三篇分享了下DDD裡面的兩個主要特性:聚合和倉儲。領域層的搭建基本完成,當然還涉及到領域事件和領域服務的部分,後面再專案搭建的過程中慢慢引入,博主的思路是先將整個架構走通,然後一步一步來新增相關元素,使架構慢慢變得豐滿。這篇打算分享下應用層的搭建。根據DDD的設計原則,應用層不包含任何領域邏輯,它主

C#系列——DDD領域驅動設計初探倉儲Repository

前言:上篇介紹了下倉儲的程式碼架構示例以及簡單分析了倉儲了使用優勢。本章還是繼續來完善下倉儲的設計。上章說了,倉儲的最主要作用的分離領域層和具體的技術架構,使得領域層更加專注領域邏輯。那麼涉及到具體的實現的時候我們應該怎麼做呢,本章就來說說倉儲裡面具體細節方便的知識。 DDD領域驅動設計初探系列文章:

C#系列——DDD領域驅動設計初探倉儲Repository

前言:上篇介紹了DDD設計Demo裡面的聚合劃分以及實體和聚合根的設計,這章繼續來說說DDD裡面最具爭議的話題之一的倉儲Repository,為什麼Repository會有這麼大的爭議,博主認為主要原因無非以下兩點:一是Repository的真實意圖沒有理解清楚,導致設計的紊亂,隨著專案的橫向和縱向擴充套件,

C#系列——DDD領域驅動設計初探Web層的搭建

前言:好久沒更新部落格了,每天被該死的業務纏身,今天正好一個模組完成了,繼續來完善我們的程式碼。之前的六篇完成了領域層、應用層、以及基礎結構層的部分程式碼,這篇打算搭建下UI層的程式碼。 DDD領域驅動設計初探系列文章: 一、UI層介紹 在DDD裡面,UI層的設計也分為BS和CS,本篇還是以Web為

C#系列——DDD領域驅動設計初探聚合

前言:又有差不多半個月沒寫點什麼了,感覺這樣很對不起自己似的。今天看到一篇博文裡面寫道:越是忙人越有時間寫部落格。呵呵,似乎有點道理,博主為了證明自己也是忙人,這不就來學習下DDD這麼一個聽上去高大上的東西。前面介紹了下MEF和AOP的相關知識,後面打算分享Automapper、倉儲模式、WCF等東西的,可是

C#系列——WebApi 異常處理解決方案

機制 輸出 ges 如果 但是 rom lba slist 解決 出處:http://www.cnblogs.com/landeanfen/p/5363846.html 閱讀目錄 一、使用異常篩選器捕獲所有異常 二、HttpResponseException自

大型Java專題(二) 軟體架構設計原則

## 前言 ​ 今天開始我們專題的第一課了,也是我開始進階學習的第一天,我們先從經典設計思想開始,看看大牛市如何寫程式碼的,提升技術審美、提高核心競爭力。本章節參考資料書籍《Spring 5核心原理》中的第一篇 Spring 內功心法(沒有電子檔,都是我取其精華並結合自己的理解,一個字一個字手敲出來的)。

設計模式外觀模式

    說明:Facade外觀類      SubSystem子系統 優點:   ①對子系統的使用變得簡單了,減少了客戶與子系統的聯絡和子系統之間的耦合。   ②降低了大型軟體系統中的編譯依賴性,並簡化了系統在不同平臺之間的移植過程。 缺點:新增子系統

Java 設計模式原型模式

參考連結:原型模式-Prototype Pattern 1. 模式概述 定義:使用原型例項指定建立物件的種類,並且通過拷貝這些原型建立新的物件。 在使用原型模式時,我們需要首先建立一個原型物件,再通過複製這個原型物件來建立更多同類型的物件。 原型模式的工作原理很簡單:將一

設計模式策略模式

介紹 策略設計模式是行為設計模式之一。當我們為特定任務使用多個演算法時,使用策略模式,客戶端決定在執行時使用的實際實現。 策略模式的最佳示例之一是Collections.sort()採用Comparator引數的方法。基於Comparator介面的不同實現,物件將以不同的方式進行排序。 例項 對於我們的示例

Java中的設計模式策略模式

策略設計模式是行為設計模式之一。當我們為特定任務使用多個演算法時,使用策略模式,客戶端決定在執行時使用的實際實現。 策略模式的最佳示例之一是Collections.sort()採用Comparator引數的方法。基於Comparator介面的不同實現,物件將以不同的方式進行排

linux裝置驅動歸納總結2.操作硬體——IO記憶體

<script>window._bd_share_config={"common":{"bdSnsKey":{},"bdText":"","bdMini":"2","bdMiniList":false,"bdPic":"","bdStyle":"0","bdSi

EF Code first 和 DDD (領域驅動設計研究)系列

發的 tex bsp cti 設計 ron 映射 developer devel 在上個公司工作時,開發公司產品的過程中,接觸到了EF Code first. 當時,整個產品的架構都是Lead developer設計建立的,自己也不是特別理解,就趕鴨子上架跟著一起開發了。

【9】C++系列泛型設計以及STL標準模板庫

1、泛型程式設計基本概念 泛型程式設計:編寫不依賴與具體資料型別的程式,將演算法從特定的資料結構中抽象出來,成為通用的。C++的模板為泛型程式設計定義了關鍵的基礎。 兩個術語:概念,模型 概念:用來界定具備一定功能的資料型別,例如:將“可以比較大小的所有資料型別(有比較

C#系列——MEF實現設計上的“鬆耦合”

前言:前篇 C#進階系列——MEF實現設計上的“鬆耦合”(一) 介紹了下MEF的基礎用法,讓我們對MEF有了一個抽象的認識。當然MEF的用法可能不限於此,比如MEF的目錄服務、目錄篩選、重組部件等高階應用在這裡就不做過多講解,因為博主覺得這些用法只有在某些特定的環境下面才會用到,著實不太普遍,感覺沒有鑽下去的

C#系列——MEF實現設計上的“鬆耦合”

前言:最近去了趟外地出差,介紹推廣小組開發的框架類產品。推廣物件是本部門在專案上面的同事——1到2年工作經驗的初級程式設計師。在給他們介紹框架時發現很多框架設計層面的知識他們都沒有接觸過,甚至沒聽說過,這下囧了~~於是乎在想該如何跟他們解釋MEF、AOP、倉儲模式等方面的東東。本來 C#基礎系列 應該還有兩篇

C#系列——MEF實現設計上的“鬆耦合”建構函式注入

前言:今天十一長假的第一天,本因出去走走,奈何博主最大的樂趣是假期坐在電腦前看各處堵車,順便寫寫部落格,有點收穫也是好的。關於MEF的知識,之前已經分享過三篇,為什麼有今天這篇?是因為昨天分享領域服務的時候,用到MEF的注入有參建構函式的方法,博主好奇心重,打算稍微深挖一下,這篇來對此知識點做個總結。 還是

C#系列——MEF實現設計上的“鬆耦合”終結篇面向介面程式設計

序:忙碌多事的八月帶著些許的倦意早已步入尾聲,金秋九月承載著抗戰勝利70週年的喜慶撲面而來。沒來得及任何準備,似乎也不需要任何準備,因為生活不需要太多將來時。每天忙著上班、加班、白加班,忘了去憤,忘了去算計所謂的價值。天津爆炸事故時刻警示著我們生命的無常,逝者安息,活著的人生活還得繼續,珍惜生命,遠離傷害。武