前言:前篇搭建了下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的使用。
一、AutoMapper
Automapper是一個object-object mapping(物件對映)工具,一般主要用於兩個物件之間資料對映和交換。當然你也可以自己通過反射去寫物件的對映,對於簡單的兩個屬性間的資料轉換,肯定沒什麼問題。但是如果遇到某些複雜的資料轉換,比如指定某一個物件的某個屬性對映到另一個物件的某一個屬性,這種情況如果我們自己手動對映,恐怕就有點麻煩了吧。既然我們有現成的工具,為什麼不用呢?
二、AutoMapper引用到專案中
向專案中新增AutoMapper的引用有兩種方式:
1、Nuget方式
在需要使用AutoMapper的專案檔案上面右鍵→管理Nuget程式包,開啟Nuget介面,搜尋Automapper,然後安裝第一個即可。如下圖:
2、程式包管理控制檯方式
點選Visual Studio的工具選單→程式包管理控制檯,然後選擇需要安裝Automapper的專案(下圖中的預設專案),最後在控制檯裡面輸入命令“Install-Package AutoMapper”命令即可按照Automapper包:
三、AutoMapper使用程式碼示例
1、最簡單的物件對映
AutoMapper使用起來還是比較簡單的,最簡單的用法你只需要兩句話:
1 2 |
var oMenu = new TB_MENU() { MENU_NAME="許可權管理", MENU_LEVEL="1" };Mapper.CreateMap(); var oDto = Mapper.Map(oMenu); |
首先建立對映,然後傳入需要對映的物件執行對映。相信在專案中使用過AutoMapper的原因肯定也寫過類似這樣的AutoMapperHelper
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
/// /// AutoMapper幫助類 /// public static class AutoMapperHelper { /// /// 單個物件對映 /// public static T MapTo(this object obj) { if (obj == null) return default(T); Mapper.CreateMap(obj.GetType(), typeof(T)); return Mapper.Map(obj); } /// /// 集合列表型別對映 /// public static List MapToList(this IEnumerable source) { Mapper.CreateMap(); return Mapper.Map>(source); } } |
當然,這是最簡單的用法,稍微複雜點的用法我們在後面慢慢介紹。
2、指定欄位的物件對映
前面說了,對於指定某一個物件的某個屬性對映到另一個物件的某一個屬性,這種場景,我們先來看看下面程式碼:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
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這一個物件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
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如何解決:
1 2 3 4 |
var oDomainUser = userRepository.Entities.FirstOrDefault(); var map = Mapper.CreateMap(); map.ForMember(d => d.DEPARTMENT_NAME, opt => opt.MapFrom(x => x.TB_DEPARTMENT.NAME)); var oDto = Mapper.Map(oDomainUser); |
通過上面的程式碼,ForMember()方法會指定哪個欄位轉換為哪個欄位,這樣就完美的將物件的層級結構由二級變成了一級(即將TB_USERS下面TB_DEPARTMENT物件的NAME值轉換成了DTO_TB_USERS的DEPARTMENT_NAME值)。除此之外,Automapper裡面還可以通過ForMember幫我們做其他很多我們想不到的事情,比如可以設定某個屬性值保留初始值,只需要通過
1 |
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,來看看程式碼吧:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
[Import] public IUserRepository userRepository { get; set; } public virtual IList Find(Expressionbool>> selector) { //得到從Web傳過來和DTOModel相關的lamaba表示式的委託 Funcbool> match = selector.Compile(); //建立對映Expression的委託 Func mapper = AutoMapper.QueryableExtensions.Extensions.CreateMapExpression(Mapper.Engine).Compile(); //得到領域Model相關的lamada Expressionbool>> lamada = ef_t => match(mapper(ef_t)); List list = userRepository.Find(lamada).ToList(); return Mapper.Map, List>(list); } |
上面方法完美實現了兩種lamada之間的轉換,但根據博主的使用經歷,這種轉換對屬性的型別有很嚴格的要求,必須保證領域model和DTO的Model同一個屬性的型別完全相同,否則容易報異常。使用的時候需要注意。實際使用的方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
public List GetDtoByLamada(IRepository oRepository, Expressionbool>> selector = null) where DomainModel : AggregateRoot where DtoModel : DTO_BASEMODEL { if (selector == null) { var lstDomainModel = oRepository.Entities.ToList(); return Mapper.Map, List>(lstDomainModel); } //得到從Web傳過來和DTOModel相關的lamaba表示式的委託 Mapper.CreateMap(); Mapper.CreateMap(); Funcbool> match = selector.Compile(); //建立對映Expression的委託 Func mapper = AutoMapper.QueryableExtensions.Extensions.CreateMapExpression(Mapper.Engine).Compile(); //得到領域Model相關的lamada Expressionbool>> lamada = ef_t => match(mapper(ef_t)); List list = oRepository.Find(lamada).ToList(); return Mapper.Map, List>(list); } |
呼叫
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 |
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 GetUsers(Expressionbool>> selector) { return base.GetDtoByLamada(userRepository, selector); } public List GetDepartments(Expressionbool>> selector) { return base.GetDtoByLamada(departmentRepository, selector); } public List GetRoles(Expressionbool>> selector) { return base.GetDtoByLamada(roleRepository, selector); } public List GetMenus(Expressionbool>> selector) { return base.GetDtoByLamada(menuRepository, selector); } #endregion } |
4、Automapper的其他應用
除了上面介紹的Automapper的幾個簡單使用,其他還有其他的一些用法。
網上很多介紹DataReader物件和實體類之間的對映:
1 2 3 4 5 6 7 |
using (IDataReader reader = db.ExecuteReader(command)) { if (reader.Read()) { return AutoMapper.Mapper.DynamicMap(reader); } } |
至此,AutoMapper的常見用法基本分享完了,至於更高階的用法,有興趣可以看看蟋蟀兄的【AutoMapper官方文件】DTO與Domin Model相互轉換(上)。雖然很多高階用法在實際專案中很難用上,但多瞭解一點似乎也並沒有壞處。
DDD領域驅動設計初探系列文章: