【.NET】AutoMapper學習記錄
在兩個不同的型別物件之間傳輸資料,通常我們會用DTOs(資料傳輸物件),AutoMapper就是將一個物件自動轉換為另一個物件的技術
背景
一些orm框架,在用到Entity的時候有一些開原始碼用到了automapper(如:nopcommence),將資料物件轉成DTO。比如在ORM中,與資料庫互動用的Model模型是具有很多屬性變數方法神馬的。而當我們與其它系統(或系統中的其它結構)進行資料互動時,出於耦合性考慮或者安全性考慮或者效能考慮(總之就是各種考慮),我們不希望直接將這個Model模型傳遞給它們,這時我們會建立一個貧血模型來儲存資料並傳遞。什麼是貧血模型?貧血模型(DTO,Data Transfer Object)就是說只包含屬性什麼的,只能儲存必須的資料,沒有其它任何的多餘的方法資料什麼的,專門用於資料傳遞用的型別物件。在這個建立的過程中,如果我們手動來進行,就會看到這樣的程式碼:
A a=new A();
a.X1=b.X1;
a.X2=b.X2;
...
...
...
return a; 太麻煩
此時,AutoMapper可以發揮的作用就是根據A的模型和B的模型中的定義,自動將A模型對映為一個全新的B模型。(不用一個屬性一個屬性的賦值)
好處:
1、 db或者模型 增加欄位時,只需在DTO內部增加對映,賦值程式碼無需修改
2、隔離,前端收集各引數,不用管後端定義的模型。前後端才用AutoMapper來做轉換。
使用
Nuget引用:AutoMapper 版本不一樣,裡面的很多方法有些不一樣
AutoMapper是基於約定的,因此在實用對映之前,我們需要先進行對映規則的配置。
我們要做的只是將要對映的兩個型別告訴AutoMapper(呼叫Mapper類的Static方法CreateMap並傳入要對映的型別):
Mapper.Initialize(cfg => { cfg.CreateMap<StudentEntity, StudentOutput>(); });
也可以將實體類 放在配置檔案MapperProfile中
Mapper.Initialize(cfg => {
cfg.AddProfile<MapperProfile>();
cfg.AddProfile<ProxyAdapterProfile>(); //可增加多個
});
注意:多次呼叫 Mapper.Initialize() 只有最後一次生效。所以只能用一個Mapper.Initialize。
【AutoMapper.7.0.1】
class MapperProfile : Profile { public MapperProfile() { CreateMap<StudentEntity, StudentOutput>(); var map = CreateMap<UploadResponseBase, UploadResult>(); //欄位名稱不一致,一次直接定義好所有欄位的對映規則 map.ConvertUsing(s => new UploadResult { IsSuccess = s.success, FileUrl = s.clientUrl, ErrorMessage = s.rawFileName , datetimeStr = (s.datetime).ToString(), }); } }View Code
【AutoMapper 4.2.1.0】
AutoMapper使用ForMember來指定每一個欄位的對映規則:
protected override void Configure() { var mapResponst = CreateMap<Response, CResponse>(); mapResponst.ForMember(dest => dest.departure_date, opt => opt.MapFrom(src => src.DepartureDate.ToString("yyyy-MM-dd HH:mm:ss"))) .ForMember(dest => dest.ticket_price, opt => opt.MapFrom(src => src.TicketPrice)); var mapContacts = CreateMap<CContacts, PassengerInputEntity>(); mapContacts.ForMember(dest => dest.FirstName, opt => opt.MapFrom(src => src.First_Name)) .ForMember(dest => dest.LastName, opt => opt.MapFrom(src => src.Last_Name)) .ForMember(dest => dest.AreaCode, opt => opt.MapFrom(src => src.Area_Code)); }View Code
然後就可以交給AutoMapper幫我們搞定一切了:
//例項化實體List
List<StudentEntity> StudentList = new List<StudentEntity>();
//模擬資料
StudentList.Add(new StudentEntity
{
Id = 1,
Age = 12,
Gander = "boy",
Name = "WangZeLing",
Say = "Only the paranoid survive",
Score = 99M
});
//AuotMapper具體使用方法 將List<StudentEntity>轉換為List<StudentOutput>
List<StudentOutput> Output = Mapper.Map<List<StudentOutput>>(StudentList);
Output.ForEach(output => Console.WriteLine(string.Format("name:{0},say:{1},score:{2}", output.Name, output.Say, output.Score)));
解釋
1、 AutoMapper給我們提供的Convention或Configuration方式並不是“異或的”,我們可以結合使用兩種方式,為名稱不同的欄位配置對映規則,而對於名稱相同的欄位則忽略配置。
2、 在對映具有相同欄位名的型別時,會自動轉換
3、 不相同名稱的屬性則需要 指定對映欄位,設定ConvertUsing或者ForMember..
4、 值為空的屬性,AutoMapper在對映的時候會把相應屬性也置為空
5、 如果傳入一個空的AddressDto,AutoMapper也會幫我們得到一個空的Address物件。
Address address = Mapper.Map<AddressDto,Address>(null);
6、不需要對映的屬性可以用Ignore忽略。【if有驗證 目標類中的所有屬性是否都被對映 時】
使用Ignore方法:
Mapper.CreateMap<Entity.Source, Entity.Destination>()
.ForMember(dest => dest.SomeValuefff, opt =>
{
opt.Ignore();
});
最佳實踐
這段內容將討論AutoMapper的規則寫在什麼地方的問題。
在上一段中,我們已經知道了如何使用AutoMapper進行簡單的物件對映,但是,在實際的專案中,我們會有很多類進行對映(從Entity轉換為Dto,或者從Entity轉換為ViewModel等),這麼多的對映如何組織將成為一個問題。
首先我們需要定義一個Configuration.cs的類,該類提供AutoMapper規則配置的入口,它只提供一個靜態的方法,在程式第一次執行的時候呼叫該方法完成配置。
當有多個Profile的時候,我們可以這樣新增:
public class Configuration
{
public static void Configure()
{
Mapper.Initialize(cfg =>
{
cfg.AddProfile<Profiles.SourceProfile>();
cfg.AddProfile<Profiles.OrderProfile>();
cfg.AddProfile<Profiles.CalendarEventProfile>();
});
}
}
在程式執行的時候,只需要呼叫Configure方法即可。
瞭解了這些實現以後,我們可以再專案中新增AutoMapper資料夾。
Configuration為我們的靜態配置入口類;Profiles資料夾為我們所有Profile類的資料夾。如果是MVC,我們需要在Global中呼叫:
AutoMapper.Configuration.Configure();
問題:Missing type map configuration or unsupported mapping
重現:本地除錯直接打開出錯的頁面,除錯發現是ok的;然後先開啟用到了mapper所在控制器對應的頁面,再去打開出錯的頁面,是報錯的。
從 GitHub 上籤出 AutoMapper 的原始碼一看 Mapper.Initialize() 的實現,恍然大悟。
public static void Initialize(Action<IMapperConfigurationExpression> config)
{
Configuration = new MapperConfiguration(config);
Instance = new Mapper(Configuration);
}
原來每次呼叫 Mapper.Initialize() 都會建立新的 Mapper 例項,也就是多次呼叫 Mapper.Initialize() 只有最後一次生效。
切記不要多處呼叫Mapper.Initialize()。
優化方法:【寫一個工具類】
需程式集:AutoMapper
/// <summary> /// 優化AutoMap對映工具,解決AutoMap只能Initialize一次的問題 /// </summary> public class AutoMapperManager { /// <summary> /// 儲存所有的profile /// </summary> static ConcurrentBag<Profile> Profiles; static AutoMapperManager() { Profiles = new ConcurrentBag<Profile>(); } /// <summary> /// 新增Profile,必須放在靜態建構函式裡 /// </summary> /// <param name="profile"></param> public static void AddProfile(Profile profile) { Profiles.Add(profile); } /// <summary> /// 初始化,可以多次呼叫,同時之前的Profile也會生效 /// </summary> public static void Initialize() { Mapper.Initialize(config => { Profiles.ToList().ForEach(file => { config.AddProfile(file); }); }); } }View Code
其他地方需要用mapper的地方 呼叫方式:
AutoMapperManager.AddProfile(new Profile1());
AutoMapperManager.AddProfile(new Profile2());
AutoMapperManager.Initialize();
參考:
https://www.cnblogs.com/jobs2/p/3503990.html