AutoMapper是用來解決對象之間映射轉換的類庫。對於我們開發人員來說,寫對象之間互相轉換的代碼是一件極其浪費生命的事情,AutoMapper能夠幫助我們節省不少時間。
一. AutoMapper解決了什么問題?
要問AutoMapper解決了什么問題? 難道不是對象映射轉換的問題嗎?
當然是,不過我們可以問深入一些,為什么項目中會出現大量的對象映射轉換?(以下對於非MVC項目也適用)
在現代的軟件開發中,項目的層級更加的細分,而不同層級之間對於對象的需求是有區別的,這就需要在不同層級間傳遞數據的時候,必須要轉換數據。
舉一些實際具體的例子:
在持久層(數據訪問層), 我們的User對象,可能是一個包含User表中所有字段的數據的對象,甚至包含了用戶的Password信息。而在界面層,我們只是需要顯示用戶的 name, email,不需要Password這些額外的信息,同時,它還需要用戶的考勤信息,而這個信息來自於另外一張表。
這個例子中,能夠發現不同層之間,我們對於數據對象的需求是不同的。
每個層都做了它們職責范圍內的事情:
持久層關注數據,所以只提供數據對象,它無需知道外層如何使用這些數據對象,也無法知道。
界面層關注數據的呈現,它只關注它要顯示的數據。
那么問題是,誰來彌補它們之間的鴻溝?DTO(Data Transfer Object)——數據傳輸對象。而AutoMapper就是解決其中涉及到的數據對象轉換的工具。
在實際開發中,如果你還可以直接在Business層或者界面層直接使用持久層的對象,因為你認為這個關系不大,整個項目都是你自己控制的,雖然 dirty,但是quick. 作為一個有些潔癖的程序員,我還是建議使用DTO在不同層級之間傳遞數據。因為當你做更高層級開發的時候,比如開發web service,WCF,Web API這些為系統外部提供接口的開發時候,你就回明白這些好的習慣和思維能夠幫助你更加好的設計這些外部接口。
二. AutoMapper如何使用?
先來看一個簡單的例子,這個例子是定義Order對象到OrderDto對象之間的映射。(我們把Order稱呼為源類,OrderDto稱呼為目標類)
Mapper.CreateMap<Order, OrderDto>();//創建映射關系Order –> OrderDto OrderDto dto = Mapper.Map<OrderDto>(order);//使用Map方法,直接將order對象裝換成OrderDto對象
智能匹配
AutoMapper能夠自動識別和匹配大部分對象屬性:
- 如果源類和目標類的屬性名稱相同,直接匹配
- 目標類型的CustomerName可以匹配源類型的Customer.Name
- 目標類型的Total可以匹配源類型的GetTotal()方法
自定義匹配規則
AutoMapper還支持自定義匹配規則
//屬性匹配,匹配源類中WorkEvent.Date到EventDate .ForMember(dest => dest.EventDate, opt => opt.MapFrom(src => src.WorkEvent.Date)) .ForMember(dest => dest.SomeValue, opt => opt.Ignore())//忽略目標類中的屬性 .ForMember(dest => dest.TotalAmount, opt => opt.MapFrom(src => src.TotalAmount ?? 0))//復雜的匹配 .ForMember(dest => dest.OrderDate, opt => opt.UserValue<DateTime>(DateTime.Now));固定值匹配
測試
當定義完規則后,可以使用下面的代碼來驗證配置是否正確。不正確拋出異常AutoMapperConfigurationException.
Mapper.AssertConfigurationIsValid();
三. AutoMapper處理多對一映射
我們開篇提到的問題中,說到界面顯示User的name, email, 還有用戶的考勤信息,而這些信息來自於2張不同的表。這就涉及到了多對一映射的問題,2個持久層對象需要映射到一個界面顯示層的對象。
假設我們的持久層對象是這樣的:
{ public int Id { get; set; } public string Name { get; set; } public string Email { get; set; } public string Passworkd { get; set; } public DateTime Birthday { get; set; } } public class Evaluation { public int Id { get; set; } public int Score { get; set; } }
在Asp.net MVC中,我的界面顯示層的ViewModel是這樣的
{ public int Id { get; set; } public string Name { get; set; } public string Email { get; set; } public int Score { get; set; } }
接下來,為了達到多對一的映射的目的,我們創建這個EntityMapper類
{ public static T Map<T>(params object[] sources) where T : class { if (!sources.Any()) { return default(T); } var initialSource = sources[0]; var mappingResult = Map<T>(initialSource); // Now map the remaining source objects if (sources.Count() > 1) { Map(mappingResult, sources.Skip(1).ToArray()); } return mappingResult; } private static void Map(object destination, params object[] sources) { if (!sources.Any()) { return; } var destinationType = destination.GetType(); foreach (var source in sources) { var sourceType = source.GetType(); Mapper.Map(source, destination, sourceType, destinationType); } } private static T Map<T>(object source) where T : class { var destinationType = typeof(T); var sourceType = source.GetType(); var mappingResult = Mapper.Map(source, sourceType, destinationType); return mappingResult as T; } }
為了實現多個源對象映射一個目標對象,我們使用了AutoMapper的方法,從不同的源對象逐一匹配一個已經存在的目標對象。下面是實際使用在MVC中的代碼:
{ var userId = 23, var user = _userRepository.Get(userId); var score = _scoreRepository.GetScore(userId); var userViewModel = EntityMapper.Map<UserViewModel>(user, score); return this.View(userViewModel); }
四. 使用Profile在Asp.net MVC項目中配置AutoMapper
Profile是AutoMapper中用來分離類型映射定義的,這樣可以讓我們的定義AutoMapper類型匹配的代碼可以更加分散,合理和易於管理。
利用Profile, 我們可以更加優雅的在MVC項目中使用我們的AutoMapper. 下面是具體的方法:
1. 在不同層中定義Profile,只定義本層中的類型映射
繼承AutoMapping的Profile類,重寫ProfileName屬性和Configure()方法。
{ public override string ProfileName { get { return GetType().Name; } } protected override void Configure() { Mapper.CreateMap...... } }
2. 創建AutoMapperConfiguration, 提供靜態方法Configure,一次加載所有層中Profile定義
public class AutoMapperConfiguration { public static void Configure() { Mapper.Initialize(x => x.AddProfile<ViewModelMappingProfile>()); Mapper.AssertConfigurationIsValid(); } }
3. 在Global.cs文件中執行
最后,在Global.cs文件中程序啟動前,調用該方法
AutoMapperConfiguration.Configuration()