應用場景
在上一篇文章——Asp.NetCore之AutoMapper基礎篇中我們簡單介紹了一些AutoMapper的基礎用法以及如何在.NetCore中實現快速開發。我相信用過AutoMapper實現模型對映之後,許多人會和我當初一樣有一種淡淡的憂愁,每次實現自定義對映都需要手寫對映規則,形如:
CreateMap<Order, OrderDTO>().ForMember(dest => dest.OrderName, src => src.MapFrom(s => s.Name))
如果有很多的模型需要對映,並且對映規則基本都一致,譬如:模型欄位不一致對映(Order.Name對映到OrderDTO.OrderName),如果存在很多類似這樣的模型屬性對映, 大量的手動編碼同樣效率很低,不禁丟擲疑問:是否可以批量動態對映呢?
AutoMapper實現動態對映
既然有了以上的場景需求,下面我們就來聊一聊如何使用AutoMapper實現動態對映。AutoMapper框架為我們提供了動態對映方法,如下
IMappingExpression CreateMap(Type sourceType, Type destinationType, MemberList memberList)
從方法入參Type型別我們可以知道,呼叫該方法時我們不需要知道對映的源模型和目標模型具體是什麼型別,這也就為我們實現批量對映提供了入口,對於一批有著同樣對映規則的模型,我們完全可以通過該來實現。那麼,我們如何批量獲取需要對映的源模型和目標模型呢?下面我們結合System.Attribute特性來給大家介紹下。
Attribute特性
可能有些人沒用過Attribute特性,我們先來簡單瞭解下。Attribute特性在.Net 反射中經常被使用,它以一種聲名式標籤的形式存在,標籤中定義了一些元素用來在程式執行時使用,它通常放置在類、屬性等元素上面並用中括號[ ]的形式表達。
特性介紹:
- 自定義特性
通常我們需要自定義特性以滿足實際需求,自定義特性時必須要繼承Attribute抽象類。
public class TypeMapperAttribute : Attribute {
}
- 預定義特性AttributeUsage
預定義特性AttributeUsage用來定義特性的一些使用規則。
[AttributeUsage(AttributeTargets.Class, Inherited = true)] public class TypeMapperAttribute : Attribute {}
常用引數:
- ValidOn 規定特性可被使用的範圍。它是列舉器 AttributeTargets的值的組合。預設值是 AttributeTargets.All表示在所有元素都可使用。如果只允許在類或者屬性上使用可以定義:AttributeTargets.Class或者AttributeTargets.Property
- AllowMultiple(可選的)bool型別。如果為 true,則該特性是多用的。預設值是 false(單用的)。
- Inherited(可選的)bool型別。如果為 true,則該特性可被派生類繼承。預設值是 false(不被繼承)。
自定義特性:
//AttributeUsage用與指定宣告的特性的使用範圍 [AttributeUsage(AttributeTargets.Class| AttributeTargets.Class, Inherited = true)] public class TypeMapperAttribute : Attribute { /// <summary> /// 源型別 /// </summary> public Type SourceType { get; set; } } //AttributeUsage用與指定宣告的特性的使用範圍 [AttributeUsage(AttributeTargets.Property, Inherited = true)] public class PropertyMapperAttribute : Attribute { /// <summary> /// 屬性名稱 /// </summary> public string SourceName { get; set; } /// <summary> /// 資料型別 /// </summary> public Type SourceDataType { get; set; } }
有了特性功能的加入,我們便可以批量獲取所有需要對映的目標模型。
//獲取所有需要依據特性進行對映的DTO類 var typeList = Assembly.GetAssembly(typeof(OrderDTO)).GetTypes().Where(t => t.GetCustomAttributes(typeof(TypeMapperAttribute)).Any()).ToList();
-
Assembly.GetAssembly(typeof(OrderDTO)).GetTypes() 獲取指定程式集下面的所有類
-
GetCustomAttributes() 獲取自定義特性
回到AutoMapper框架的動態對映方法CreateMap(Type sourceType, Type destinationType, MemberList memberList),我們已經有了批量的目標模型,還缺少批量的源模型。很顯然,只要在目標模型上加上“特性”我們就能很容易拿到目標模型所對應的源模型。
新建基於特性的目標模型:
/// <summary> /// 源模型Order 對映到 目標模型OrderBatchDTO /// </summary> [TypeMapper(SourceType = typeof(Order))] public class OrderBatchDTO { public int Id { get; set; } /// <summary> /// Order.Name 對映到 OrderBatchDTO.OrderName /// </summary> [PropertyMapper(SourceName = "Name")] public string OrderName { get; set; }
public decimal Price { get; set; } /// <summary> /// Order.CreateTime時間格式 對映到 OrderBatchDTO.CreateTime自定義字串格式 /// </summary> [PropertyMapper(SourceDataType = typeof(DateTime))] public string CreateTime { get; set; }
public int CustomId { get; set; } }
通過TypeMapperAttribute特性,我們可以拿到目標模型所對應的源模型;
通過PropertyMapperAttribute特性,我們可以拿到對映規則中定義的源模型欄位名稱、源模型欄位型別;
自定義動態對映配置檔案
接下來,自定義動態對映配置檔案,繼承AutoMapper的Profile配置類。
public class BatchMapperProfile : Profile { public BatchMapperProfile() { InitMapper(); } public void InitMapper() { //獲取所有需要依據特性進行對映的DTO類 var typeList = Assembly.GetAssembly(typeof(OrderDTO)).GetTypes().Where(t => t.GetCustomAttributes(typeof(TypeMapperAttribute)).Any()).ToList(); typeList.ForEach(type => { //獲取類指定的特性 var attribute = (TypeMapperAttribute)type.GetCustomAttributes(typeof(TypeMapperAttribute)).FirstOrDefault(); if (attribute == null || attribute.SourceType == null) return; //類對映 var mapper = CreateMap(attribute.SourceType, type); //處理類中對映規則不同的屬性 var propertyAttributes = type.GetProperties().Where(p => p.GetCustomAttributes(typeof(PropertyMapperAttribute)).Any()).ToList(); propertyAttributes.ForEach(property => { //獲取屬性指定特性 var propertyAttribute = (PropertyMapperAttribute)property.GetCustomAttributes(typeof(PropertyMapperAttribute)).FirstOrDefault(); if (propertyAttribute == null) return; if (!string.IsNullOrEmpty(propertyAttribute.SourceName)) { //屬性名稱自定義對映 mapper.ForMember(property.Name, src => src.MapFrom(propertyAttribute.SourceName)); } if (propertyAttribute.SourceDataType != null && propertyAttribute.SourceDataType == typeof(DateTime)) { //DateTime資料型別 對映 自定義字串格式 mapper.ForMember(property.Name, src => src.ConvertUsing(new FormatBatchConvert())); } }); }); } } /// <summary> /// DateTime對映到String /// </summary> public class FormatBatchConvert : IValueConverter<DateTime, string> { public string Convert(DateTime sourceMember, ResolutionContext context) { if (sourceMember == null) return DateTime.Now.ToString("yyyyMMddHHmmssfff"); return sourceMember.ToString("yyyyMMddHHmmssfff"); } }
動態對映配置檔案中主要是用了一些反射的基礎知識,包括獲取型別,獲取指定型別屬性,獲取型別特性,獲取屬性特性等,這裡就不一一介紹了。
其中,如下兩個成員自定義對映規則,實際上就是我們上一篇博文中介紹的兩種常用方式,差別只是動態對映方法提供的呼叫方式不同而已。
-
mapper.ForMember(property.Name, src => src.MapFrom(propertyAttribute.SourceName));
-
mapper.ForMember(property.Name, src => src.ConvertUsing(new FormatBatchConvert()));
有了我們自定義的動態對映配置檔案之後,我們只需要在服務中依賴注入一下即可使用。.NetCore專案中如何依賴注入AutoMapper可參見上一篇博文,我這裡就不再具體描述,下面我們直接使用看效果。
/// <summary> /// 批量動態對映 /// </summary> /// <returns></returns> public async Task<List<OrderBatchDTO>> QueryBatch() { var orderList = await dBContext.DB.Queryable<Order>().ToListAsync(); var orderDtoList = mapper.Map<List<OrderBatchDTO>>(orderList); return await Task.FromResult(orderDtoList); }
其中,mapper是我們依賴注入的AutoMapper例項。
實現效果
1)“源模型”Order型別中的Name屬性值 對映到 “目標模型”OrderBatchDTO型別中的OrderName
2)“源模型”Order型別中的CreateTime屬性DateTime資料型別 對映到 “目標模型”OrderBatchDTO型別中的CreateTime屬性string資料型別
小結
本篇文章中,我們介紹了基於AutoMapper如何實現批量動態對映,比較適用於有很多模型需要對映且每個模型對映規則比較相同的應用場景。如果對映的模型數量較少或者對映規則五花八門,我們大可不必大費周折,手動編碼也有它存在的意義。文章案例中我只用到了一對模型對映,大家可能感受不深,感興趣的小夥伴可以看下博文原始碼,裡面包含了多個動態對映類,小弟不才,在此獻上原始碼地址:https://github.com/chenxf1117/Asp.NetCore-AutoMapper。