在上一篇文章——Asp.NetCore之AutoMapper基礎篇中咱們簡單介紹了一些AutoMapper的基礎用法以及如何在.NetCore中實現快速開發。我相信用過AutoMapper實現模型映射以後,許多人會和我當初同樣有一種淡淡的憂愁,每次實現自定義映射都須要手寫映射規則,形如:html
CreateMap<Order, OrderDTO>().ForMember(dest => dest.OrderName, src => src.MapFrom(s => s.Name))
若是有不少的模型須要映射,而且映射規則基本都一致,譬如:模型字段不一致映射(Order.Name映射到OrderDTO.OrderName),若是存在不少相似這樣的模型屬性映射, 大量的手動編碼一樣效率很低,不由拋出疑問:是否能夠批量動態映射呢?git
既然有了以上的場景需求,下面咱們就來聊一聊如何使用AutoMapper實現動態映射。AutoMapper框架爲咱們提供了動態映射方法,以下github
IMappingExpression CreateMap(Type sourceType, Type destinationType, MemberList memberList)
從方法入參Type類型咱們能夠知道,調用該方法時咱們不須要知道映射的源模型和目標模型具體是什麼類型,這也就爲咱們實現批量映射提供了入口,對於一批有着一樣映射規則的模型,咱們徹底能夠經過該來實現。那麼,咱們如何批量獲取須要映射的源模型和目標模型呢?下面咱們結合System.Attribute特性來給你們介紹下。app
可能有些人沒用過Attribute特性,咱們先來簡單瞭解下。Attribute特性在.Net 反射中常常被使用,它以一種聲名式標籤的形式存在,標籤中定義了一些元素用來在程序運行時使用,它一般放置在類、屬性等元素上面並用中括號[ ]的形式表達。框架
特性介紹:async
一般咱們須要自定義特性以知足實際需求,自定義特性時必需要繼承Attribute抽象類。編碼
public class TypeMapperAttribute : Attribute {
}
預約義特性AttributeUsage用來定義特性的一些使用規則。spa
[AttributeUsage(AttributeTargets.Class, Inherited = true)] public class TypeMapperAttribute : Attribute {}
經常使用參數:code
自定義特性:orm
//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。