應用場景
在上一篇文章——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。