應用場景
現在由於前后端技術的分離,后端程序員在使用ORM框架開發后台API接口的時候,往往會將數據庫的“數據模型”直接提供給前端。而大多數時候,可能這些數據並不能夠滿足前端展示的需求,有時候可能需要在“數據模型”的基礎上,加幾個字段或者改幾個字段展示名稱或者字段展示風格,以滿足前端“視圖模型”的需求。遇到這種情況,后端往往需要同時定義“數據模型”和“視圖模型”,並在兩者之間做大量的字段賦值工作,如果“數據模型”和“視圖模型”差別不大的話,這無疑很耗費心力,而且容易出錯。
我們先來理解兩個概念“數據模型”和“視圖模型”:
“數據模型”:最簡單的一種“”數據模型“”你可以把它當成是數據庫表對象模型(數據模型字段一一對應數據庫表結構,是數據庫表的一種表現形式),使用ORM的小伙伴應該都知道,通過ORM數據庫模型可以直接映射到數據表結構,可以直接操作數據庫。
“視圖模型”:通過這個名稱我們很容易理解,視圖模型是前端展示頁面中所需元素的一個集合。
當我們將“數據模型”轉換成“視圖模型”提供給前端的時候,務必會產生大量的模型字段賦值的工作,模型很大的時候是不是想死的心都有了。。。AutoMapper的出現正是解決了兩個模型之間枯燥無味的轉換工作,用戶只需要簡單設置一下轉換規則即可,避免了我們每次都采用手工編寫代碼的方式進行轉換。
本文項目源碼:https://github.com/chenxf1117/Asp.NetCore-AutoMapper
.NetCore快速上手
1.創建項目
添加數據庫“數據模型”:
/// <summary> /// 訂單表 /// </summary> public class Order { /// <summary> /// ID /// </summary> public int Id { get; set; } /// <summary> /// 訂單名稱 /// </summary> public string Name { get; set; } /// <summary> /// 價格 /// </summary> public decimal Price { get; set; } public DateTime CreateTime { get; set; } public int CustomId { get; set; }
}
/// <summary> /// 訂單子表 /// </summary> public class OrderItem { public int ItemId { get; set; } public int OrderId { get; set; } public decimal Price { get; set; } /// <summary> /// 創建時間 /// </summary> public DateTime CreateTime { get; set; } }
2.添加AutoMapper依賴包
第二個包是.NetCore依賴注入使用。
3.項目中添加“視圖模型”
/// <summary> /// 訂單映射模型 /// </summary> public class OrderDTO { public int Id { get; set; } /// <summary> /// 訂單名稱 /// </summary> public string OrderName { get; set; } public decimal Price { get; set; } public DateTime CreateTime { get; set; } public int CustomId { get; set; } }
/// <summary> /// 訂單子表映射模型 /// </summary> public class OrderItemDTO { public int ItemId { get; set; } public int OrderId { get; set; } public decimal Price { get; set; } /// <summary> /// 創建時間 /// </summary> public string CreateTime { get; set; } }
4.添加自定義AutoMapper映射文件(OrderMapperProfile)
在.NetCore我們需要創建一個自己的映射配置類,該配置類必須繼承AutoMapper的Profile抽象類,然后才能通過依賴注入AutoMapper的方式調用。
/// <summary> /// 映射配置 /// </summary> public class OrderMapperProfile : Profile { public OrderMapperProfile() { //字段名稱不一致 Name映射到OrderName CreateMap<Order, OrderDTO>().ForMember(dest => dest.OrderName, src => src.MapFrom(s => s.Name)); //字段類型不一致 DateTime映射到String CreateMap<OrderItem, OrderItemDTO>().ForMember(dest => dest.CreateTime, src => src.ConvertUsing(new FormatConvert())); } } /// <summary> /// DateTime映射到String /// </summary> public class FormatConvert : IValueConverter<DateTime, string> { public string Convert(DateTime sourceMember, ResolutionContext context) { if (sourceMember == null) return DateTime.Now.ToString("yyyyMMddHHmmssfff"); return sourceMember.ToString("yyyyMMddHHmmssfff"); } }
1)自定義映射文件需繼承AutoMapper抽象類“Profile”
2)調用方法CreateMap實現模型映射
public IMappingExpression<TSource, TDestination> CreateMap<TSource, TDestination>(MemberList memberList); public IMappingExpression<TSource, TDestination> CreateMap<TSource, TDestination>();
TSource代表“數據模型”,TDestination代表“視圖模型”,MemberList可選,默認0
public enum MemberList { /// <summary> /// 檢查是否已映射所有目標成員 /// </summary> Destination = 0, /// <summary> /// 檢查是否已映射所有源成員 /// </summary> Source = 1, /// <summary> /// 既不檢查源成員也不檢查目標成員,跳過驗證 /// </summary> None = 2 }
例如:
CreateMap<Order, OrderDTO>();
3)調用ForMember自定義兩個模型之間映射規則。
因為 AutoMapper 默認是通過匹配字段名稱和類型進行自動匹配,所以如果你進行轉換的兩個類的中的某些字段名稱不一樣,這里我們就需要進行手動的編寫轉換規則。
IMappingExpression<TSource, TDestination> ForMember<TMember>(Expression<Func<TDestination, TMember>> destinationMember, Action<IMemberConfigurationExpression<TSource, TDestination, TMember>> memberOptions);
其中比較常用的:
1.兩個映射模型屬性/字段名稱不一致。
//字段名稱不一致 源類型Name映射到目標類型OrderName CreateMap<Order, OrderDTO>().ForMember(dest => dest.OrderName, src => src.MapFrom(s => s.Name));
2.兩個映射模型屬性/字段數據類型或展現形式不一致。
//字段類型不一致 源類型DateTime映射到目標類型String字符串 CreateMap<OrderItem, OrderItemDTO>().ForMember(dest => dest.CreateTime, src => src.ConvertUsing(new FormatConvert()));
ConvertUsing()方法中需實現成員參數接口IValueConverter中的Convert方法,實現自定義轉換
void ConvertUsing<TSourceMember>(IValueConverter<TSourceMember, TMember> valueConverter);
public interface IValueConverter<in TSourceMember, out TDestinationMember> { TDestinationMember Convert(TSourceMember sourceMember, ResolutionContext context); }
自定義實現接口Convert方法,實現DateTime轉string字符串:
/// <summary> /// DateTime映射到String /// </summary> public class FormatConvert : IValueConverter<DateTime, string> { public string Convert(DateTime sourceMember, ResolutionContext context) { if (sourceMember == null) return DateTime.Now.ToString("yyyyMMddHHmmssfff"); return sourceMember.ToString("yyyyMMddHHmmssfff"); } }
5.配置好映射文件后,.NetCore中需要注冊我們剛才的自定義Profile
//注冊AutoMapper //方式1 指定映射配置文件 多個可用數組表示 services.AddAutoMapper(typeof(OrderMapperProfile)); //方式2 注冊程序集下面所有映射文件 services.AddAutoMapper(Assembly.Load("程序集dll名稱"))
6.最后我們可以通過注入AutoMapper的方式,在業務邏輯層使用我們的自定義映射配置文件Profile
public class OrderService : IOrderService { private readonly IMapper mapper; /// <summary> /// 構造函數注入Mapper /// </summary> /// <param name="_dBContext"></param> /// <param name="_mapper"></param> public OrderService(IMapper _mapper) { mapper = _mapper; } /// <summary> /// 名稱不一致 映射 /// </summary> /// <returns></returns> public async Task<List<OrderDTO>> Query() { //ORM獲取數據模型數據 var orderList = await dBContext.DB.Queryable<Order>().ToListAsync(); //DTO映射 var orderDtoList = mapper.Map<List<OrderDTO>>(orderList); return await Task.FromResult(orderDtoList); } /// <summary> /// 數據類型/展現形式不一致 映射 /// </summary> /// <returns></returns> public async Task<List<OrderItemDTO>> QueryItem() { var orderList = await dBContext.DB.Queryable<OrderItem>().ToListAsync(); var orderDtoList = mapper.Map<List<OrderItemDTO>>(orderList); return await Task.FromResult(orderDtoList); } }
7.實現效果
1)“數據模型”Order類型中的Name屬性值 映射到 “視圖模型”OrderDTO類型中的OrderName
2)“數據模型”OrderItem類型中的CreateTime屬性DateTime數據類型 映射到 “視圖模型”OrderItemDTO類型中的CreateTime屬性string數據類型
小結
本篇文章主要簡單介紹下在Asp.NetCore項目中如何快速上手AutoMapper,總體來看想要上手還是比較簡單,只要掌握好幾個經常使用的映射規則就可以了。當然,AutoMapper為我們准備了各種自定規則的入口,有興趣的小伙伴可以下載源碼,源碼中包含了很多測試用例,學習起來也比較容易理解。附上AutoMapper源碼地址:https://github.com/AutoMapper/AutoMapper