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