关于对象映射(Dto->model) 思路的一些想法
最近粗浅的学习了下AutoMapper 这个做对象映射的第三方工具,觉得非常方便使用,所以简单的总结了一下我能想到的简单的对象映射的方式。
占时先不考虑源对象成员到目标对象成员的指定映射(即成员名不一致),先准备好两个类Students-StudentsDto;Teachers-TeachersDto
1 public class Students 2 { 3 public int No { get; set; } 4 public string Name { get; set; } 5 public bool Gender { get; set; } 6 public string Class { get; set; } 7 8 public string _remark; 9 } 10 11 public class StudentsDto 12 { 13 public int No { get; set; } 14 public string Name { get; set; } 15 public bool Gender { get; set; } 16 public string Class { get; set; } 17 18 public string _remark; 19 } 20 21 public class Teachers 22 { 23 public int No { get; set; } 24 25 public string Course { get; set; } 26 27 public string Name { get; set; } 28 } 29 30 public class TeachersDto 31 { 32 public int No { get; set; } 33 34 public string Course { get; set; } 35 36 public string Name { get; set; } 37 }
我们先使用普通的对象装载方式:
1 StudentsDto studentsDto = new StudentsDto { No = 1, Name = "Epic", Gender = true, Class = "家里蹲一班", _remark = "逗比" }; 2 TeachersDto teachersDto = new TeachersDto { No = 2, Name = "Eleven", Course = ".net" }; 3 Students students = new Students 4 { 5 No = studentsDto.No, 6 Name = studentsDto.Name, 7 Gender = studentsDto.Gender, 8 Class = studentsDto.Class, 9 _remark = studentsDto._remark 10 }; 11 Teachers teachers = new Teachers 12 { 13 No = teachersDto.No, 14 Name = teachersDto.Name, 15 Course = teachersDto.Course 16 };
总结:其思路无非就是先new一个对象实例,然后将该实例的成员一一赋值。
1.通过反射的方式来实现对象映射,是我们最容易想到的方式,思路也很简单
1 public static TModel Trans<TModel, TModelDto>(TModelDto dto) 2 where TModel : class 3 where TModelDto : class 4 { 5 TModel model = Activator.CreateInstance(typeof(TModel)) as TModel; 6 //获取TModel的属性集合 7 PropertyInfo[] modlePropertys = typeof(TModel).GetProperties(); 8 //获取TModelDto的属性集合 9 Type type = dto.GetType(); 10 PropertyInfo[] propertys = type.GetProperties(); 11 foreach (var property in propertys) 12 { 13 foreach (var mproperty in modlePropertys) 14 { 15 //如果属性名称一致,则将该属性值赋值到TModel实例中 16 //这里可以用Attribute来实现成员的自定义映射 17 if (property.Name.Equals(mproperty.Name)) 18 { 19 mproperty.SetValue(model, property.GetValue(dto)); 20 break; 21 } 22 } 23 } 24 25 //获取TModel的字段集合 26 FieldInfo[] modelfieldInfos = typeof(TModel).GetFields(); 27 //获取TModelDto的字段集合 28 FieldInfo[] fieldInfos = type.GetFields(); 29 foreach (var field in fieldInfos) 30 { 31 foreach (var mfield in modelfieldInfos) 32 { 33 //如果字段名称一致,则将该字段值赋值到TModel实例中 34 if (field.Name.Equals(mfield.Name)) 35 { 36 mfield.SetValue(model, field.GetValue(dto)); 37 break; 38 } 39 } 40 } 41 return model; 42 }
总结:通过反射来创建对象实例,然后将实例成语的值通过反射的方式获取并赋值。
2.通过序列号的方式,对象类型可以转换成json字符串,然后再由json字符串转换成所需的对象不就可以了么
1 public static TModel Trans<TModel, TModelDto>(TModelDto dto) 2 where TModel : class 3 where TModelDto : class 4 { 5 return JsonConvert.DeserializeObject<TModel>(JsonConvert.SerializeObject(dto)); 6 }
总结:通过序列号然后反序列化,这样使用感觉并不是Newtonsoft.Json的初衷,不知道性能到底如何呢?
3.使用Expression表达式的方式来解决,将所需实例对象new、赋值的过程先写入表达式,然后生成lambda表达式,最后编译该表达式生成委托,invoke即可
1 public static class ExpressionAndSeesionMethod 2 3 { 4 public static Dictionary<string, object> _dictionary = new Dictionary<string, object>(); 5 6 public static TModel Trans<TModel, TModelDto>(TModelDto dto) 7 { 8 Type modelType = typeof(TModel); 9 Type modelDtoType = typeof(TModelDto); 10 11 //如果_dictionary中不存在该key,则存进去 12 string key = $"{modelDtoType.Name}-->{modelType.Name}"; 13 if (!_dictionary.ContainsKey(key)) 14 { 15 //创建一个lambda参数x,定义的对象为TModelDto 16 ParameterExpression parameterExpression = Expression.Parameter(modelDtoType, "x"); 17 //开始生成lambda表达式 18 List<MemberBinding> list = new List<MemberBinding>(); 19 foreach (var item in modelType.GetProperties()) 20 { 21 //为x参数表达式生成一个属性值 22 MemberExpression property = Expression.Property(parameterExpression, modelDtoType.GetProperty(item.Name)); 23 //将该属性初始化 eg:No=x.No 24 MemberBinding memberBinding = Expression.Bind(item, property); 25 list.Add(memberBinding); 26 } 27 28 foreach (var item in typeof(TModel).GetFields()) 29 { 30 //为x参数表达式生成一个字段值 31 MemberExpression field = Expression.Field(parameterExpression, modelDtoType.GetField(item.Name)); 32 //将该字段初始化 33 MemberBinding memberBinding = Expression.Bind(item, field); 34 list.Add(memberBinding); 35 } 36 //调用构造函数,初始化一个TModel eg: new{No=x.No...} 37 MemberInitExpression memberInitExpression = Expression.MemberInit(Expression.New(modelType), list); 38 //创建lambda表达式 eg: x=>new{ No=x.No...} 39 Expression<Func<TModelDto, TModel>> lambda = Expression.Lambda<Func<TModelDto, TModel>>(memberInitExpression, parameterExpression); 40 //将lambda表达式生成委托 41 Func<TModelDto, TModel> func = lambda.Compile(); 42 _dictionary[key] = func; 43 } 44 return ((Func<TModelDto, TModel>)_dictionary[key]).Invoke(dto); 45 } 46 }
总结:使用表达式树的方式,可以将生成的委托保存起来,这里使用dictionary字典,在需要使用的时候调用即可(一次生成委托,后续可多次使用);既然是多个委托,那是不是可以使用的泛型委托Func<TIn,TResult>呢?
使用泛型缓存:
1 /// <summary> 2 ///泛型委托基于泛型类之上 3 ///泛型静态类在确定参数类型的时候会调用其静态函数 4 ///在执行委托时,泛型委托会内置查找相应的委托来执行 5 /// </summary> 6 public static class ExpressionAndFuncMethod<TModel, TModelDto> 7 where TModel : class 8 where TModelDto : class 9 { 10 static ExpressionAndFuncMethod() 11 { 12 ExpressionMapper(); 13 } 14 15 public static Func<TModelDto, TModel> _func = null; 16 17 public static void ExpressionMapper() 18 { 19 Type modelType = typeof(TModel); 20 Type modelDtoType = typeof(TModelDto); 21 22 //创建一个lambda参数x,定义的对象为TModelDto 23 ParameterExpression parameterExpression = Expression.Parameter(modelDtoType, "x"); 24 //开始生成lambda表达式 25 List<MemberBinding> list = new List<MemberBinding>(); 26 foreach (var item in modelType.GetProperties()) 27 { 28 //为x参数表达式生成一个属性值 29 MemberExpression property = Expression.Property(parameterExpression, modelDtoType.GetProperty(item.Name)); 30 //将该属性初始化 eg:No=x.No 31 MemberBinding memberBinding = Expression.Bind(item, property); 32 list.Add(memberBinding); 33 } 34 35 foreach (var item in typeof(TModel).GetFields()) 36 { 37 //为x参数表达式生成一个字段值 38 MemberExpression field = Expression.Field(parameterExpression, modelDtoType.GetField(item.Name)); 39 //将该字段初始化 40 MemberBinding memberBinding = Expression.Bind(item, field); 41 list.Add(memberBinding); 42 } 43 //调用构造函数,初始化一个TModel eg: new{No=x.No...} 44 MemberInitExpression memberInitExpression = Expression.MemberInit(Expression.New(modelType), list); 45 //创建lambda表达式 eg: x=>new{ No=x.No...} 46 Expression<Func<TModelDto, TModel>> lambda = Expression.Lambda<Func<TModelDto, TModel>>(memberInitExpression, parameterExpression); 47 //将lambda表达式生成委托 48 _func = lambda.Compile(); 49 } 50 51 public static TModel Trans(TModelDto dto) 52 { 53 if (_func != null) 54 return _func(dto); 55 return default(TModel); 56 } 57 }
总结:使用泛型委托时,即用到了其泛型缓存,在调用指定参数的委托时,能快速查找并调用(这个性能应该是高于使用字典的查找)
4.使用AutoMapper
1 public static class AutoMapperMethod 2 { 3 /// <summary> 4 /// AutoMapper 必须先创建映射 5 /// </summary> 6 /// <param name="dictionary"></param> 7 public static void Init(Dictionary<Type, Type> dictionary) 8 { 9 AutoMapper.Mapper.Initialize(x => 10 { 11 foreach (var item in dictionary) 12 { 13 x.CreateMap(item.Key, item.Value); 14 } 15 }); 16 } 17 18 19 public static TModel Trans<TModel, TModelDto>(TModelDto dto) 20 where TModel : class 21 where TModelDto : class 22 { 23 return AutoMapper.Mapper.Map<TModelDto, TModel>(dto); 24 } 25 }
总结:AutoMapper先创建再调用的原则,非常适合core项目的 注册-调用 思想,在Configure中进行注册,然后使用时Map即可,AutoMap使用emit代码开发(不太明白),性能很好,是现在最流行的映射工具。
最后,来看一下以上几种方式的性能对比吧,测试条件是将StudentsDto实例转换成Students实例,将TeachersDto实例转换成Teachers实例,各转换50万次,耗时如下图:
从上往下依次是:普通类型装载、反射、序列化、表达式缓存、表达式泛型缓存、AutoMapper
由此可见,反射和序列化是比较慢的,表达式和AutoMapper的表现差不多,一般项目中,类型映射的次数也不会很大,使用AutoMapper就已经非常够用了。
本人不才,还希望园内技术牛人多多指正。