方式一:自定义1
方法类:
using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations.Schema; using System.Linq; using System.Linq.Expressions; using System.Reflection; namespace NetFive.UnitTest { /// <summary> /// 关于对象转换已经有不少轮子(AutoMapper,TinyMapper) .出于项目需要,手动造一个简单轮子。先贴代码 /// 1.采用静态泛型类缓存,避免了拆箱装箱操作。 /// 2.对于转换对象中有,字段名一样但是类型不一样的类时仍可以用 /// </summary> /// <typeparam name="TSource"></typeparam> /// <typeparam name="TTarget"></typeparam> public static class Mapper<TSource, TTarget> where TSource : class where TTarget : class { public readonly static Func<TSource, TTarget> Map; static Mapper() { if (Map == null) Map = GetMap(); } private static Func<TSource, TTarget> GetMap() { var sourceType = typeof(TSource); var targetType = typeof(TTarget); var parameterExpression = Expression.Parameter(sourceType, "p"); var memberInitExpression = GetExpression(parameterExpression, sourceType, targetType); var lambda = Expression.Lambda<Func<TSource, TTarget>>(memberInitExpression, parameterExpression); return lambda.Compile(); } /// <summary> /// 根据转换源和目标获取表达式树 /// </summary> /// <param name="parameterExpression">表达式参数p</param> /// <param name="sourceType">转换源类型</param> /// <param name="targetType">转换目标类型</param> /// <returns></returns> private static MemberInitExpression GetExpression(Expression parameterExpression, Type sourceType, Type targetType) { var memberBindings = new List<MemberBinding>(); foreach (var targetItem in targetType.GetProperties().Where(x => x.PropertyType.IsPublic && x.CanWrite)) { var sourceItem = sourceType.GetProperty(targetItem.Name); //判断实体的读写权限 if (sourceItem == null || !sourceItem.CanRead || sourceItem.PropertyType.IsNotPublic) continue; //标注NotMapped特性的属性忽略转换 if (sourceItem.GetCustomAttribute<NotMappedAttribute>() != null) continue; var propertyExpression = Expression.Property(parameterExpression, sourceItem); //判断都是class 且类型不相同时 if (targetItem.PropertyType.IsClass && sourceItem.PropertyType.IsClass && targetItem.PropertyType != sourceItem.PropertyType) { if (targetItem.PropertyType != targetType)//防止出现自己引用自己无限递归 { var memberInit = GetExpression(propertyExpression, sourceItem.PropertyType, targetItem.PropertyType); memberBindings.Add(Expression.Bind(targetItem, memberInit)); continue; } } if (targetItem.PropertyType != sourceItem.PropertyType) continue; memberBindings.Add(Expression.Bind(targetItem, propertyExpression)); } return Expression.MemberInit(Expression.New(targetType), memberBindings); } } }
测试类:
using System; using System.ComponentModel.DataAnnotations.Schema; namespace NetFive.UnitTest { public class AddEmployeeDto { public AddEmployeeDto() { EmpId = 1; EmpNo = "1001"; EmpName = "网易"; Gender = Genders.男; EntryDate = DateTime.Now; QuitDate = null; Outsource = true; } /// <summary> /// 编号 /// </summary> public int EmpId { get; set; } /// <summary> /// 工号 /// </summary> public string EmpNo { get; set; } /// <summary> /// 姓名 /// </summary> [NotMapped]//标注为 NotMapped 特性时,不转换赋值 public string EmpName { get; set; } /// <summary> /// 性别 /// </summary> public Genders Gender { get; set; } /// <summary> /// 入职时间 /// </summary> public DateTime EntryDate { get; set; } /// <summary> /// 离职时间 /// </summary> public DateTime? QuitDate { get; set; } /// <summary> /// 是否外包 /// </summary> public bool Outsource { get; set; } public override string ToString() { return EmpId + "-" + EmpNo + "-" + EmpName + "-" + Gender.ToString() + EntryDate.ToString() + "-" + (QuitDate.HasValue ? QuitDate.ToString() : "") + "-" + Outsource; } } }
using System; using System.Collections.Generic; namespace NetFive.UnitTest { public class EmployeeInfo { /// <summary> /// 编号 /// </summary> public int EmpId { get; set; } /// <summary> /// 工号 /// </summary> public string EmpNo { get; set; } /// <summary> /// 姓名 /// </summary> /// </summary> public string EmpName { get; set; } /// <summary> /// 性别 /// </summary> public Genders Gender { get; set; } /// <summary> /// 入职时间 /// </summary> public DateTime EntryDate { get; set; } /// <summary> /// 离职时间 /// </summary> public DateTime? QuitDate { get; set; } /// <summary> /// 是否外包 /// </summary> public bool Outsource { get; set; } public static IList<EmployeeInfo> ListEmployee(int length) { IList<EmployeeInfo> employeeInfos = new List<EmployeeInfo>(); EmployeeInfo entity = new EmployeeInfo(); for (int i = 1; i <= length; i++) { entity.EmpId = i; entity.EmpNo = "No" + i; entity.EmpName = "Name" + i; entity.Gender = i % 2 == 0 ? Genders.女 : Genders.男; entity.EntryDate = DateTime.Now.AddDays(-i); entity.Outsource = i % 2 == 0; employeeInfos.Add(entity); } return employeeInfos; } public override string ToString() { return EmpId + "-" + EmpNo + "-" + EmpName + "-" + Gender.ToString() + EntryDate.ToString() + "-" + (QuitDate.HasValue ? QuitDate.ToString() : "") + "-" + Outsource; } } /// <summary> /// 性别 /// </summary> public enum Genders { 女 = 0, 男 = 1 } }
测试:
//单对象 //多对象需要循环,但查询多对象时建议使用强类型【直接输出Dto】 //参数为null时会报错,在使用方法前需要判断是否为null AddEmployeeDto add = new AddEmployeeDto(); EmployeeInfo entity = Mapper<AddEmployeeDto, EmployeeInfo>.Map(add); Console.WriteLine(add.ToString()); Console.WriteLine(entity.ToString());
方式2:自定义2
自定义类:
using System; using System.Collections.Generic; namespace XXX.Common { /// <summary> /// 常用工具 /// </summary> public class XXXUtil { #region 克隆对象 /// <summary> /// 克隆对象 /// </summary> /// <param name="target">目标</param> /// <param name="source">数据源</param> public static void CopyModel(object target, object source) { Type type1 = target.GetType(); Type type2 = source.GetType(); foreach (var mi in type2.GetProperties()) { var des = type1.GetProperty(mi.Name); if (des != null) { des.SetValue(target, mi.GetValue(source, null), null); } } } #endregion #region 克隆对象列表 public static List<T> Clone<T, TEntity>(IList<TEntity> list) where T : new() { if (list.Count == 0) { return new List<T>(); } List<T> items = new List<T>(); foreach (var m in list) { var model = new T(); var ps = model.GetType().GetProperties(); var properties = m.GetType().GetProperties(); foreach (var p in properties) { foreach (var pm in ps) { if (pm.Name == p.Name) { pm.SetValue(model, p.GetValue(m)); } } } items.Add(model); } return items; } #endregion }
调用方式:
单对象: EmployeeInfo addEmployeeInfo = new EmployeeInfo(); SupernodeUtil.CopyModel(addEmployeeInfo, entity); 多对象: var datas = SupernodeUtil.Clone<EmployeeShowDto, EmployeeInfo>(employeeInfos);
方式3:AutoMapper
NuGet包:[XXX.Service]
AutoMapper AutoMapper.Extensions.Microsoft.DependencyInjection
注意:左为要转换的数据类型,右边是转换后的数据类型。
WebApi项目下新建文件夹:【Custom/Mapper】
using AutoMapper; using NetFive.Model.Entity; using NetFive.Service.Employee.Dto; namespace NetFive.WebApi.Custom.Mapper { public class AutoMapperConfigs : Profile { /// <summary> /// 配置互相转换的类 /// </summary> public AutoMapperConfigs() { CreateMap<AddEmployeeDto, EmployeeInfo>(); CreateMap<EmployeeInfo, EmployeeShowDto>(); } } }
Startup注册:
#region AutoMapper //添加对AutoMapper的支持 services.AddAutoMapper(typeof(AutoMapperConfigs)); #endregion
调用方式:
private readonly IMapper _mapper; public EmployeeService(IMapper mapper) { _mapper = mapper; } 单对象: EmployeeInfo entity = _mapper.Map<AddEmployeeDto, EmployeeInfo>(add); 多对象: IList<EmployeeInfo> employeeInfos = EmployeeInfo.ListEmployee(100); IList<EmployeeShowDto> showDtos = _mapper.Map<IList<EmployeeShowDto>>(employeeInfos);
当参数为null时:
方式4:TinyMapper【实体类用上面的】
NuGet包:
TinyMapper
调用方式:
AddEmployeeDto add = new AddEmployeeDto(); TinyMapper.Bind<AddEmployeeDto, EmployeeInfo>(); var info = TinyMapper.Map<EmployeeInfo>(add); Console.WriteLine(info.ToString());
忽略某些字段或者绑定不同字段:
//对象名称不一样,而且需要忽略某些字段 //比如修改EmployeeInfo 的EmpName字段为Name字段 AddEmployeeDto add = new AddEmployeeDto(); TinyMapper.Bind<AddEmployeeDto, EmployeeInfo>(config => { config.Ignore(x => x.EmpId);//忽略 EmpId 字段 config.Bind(x => x.EmpName, y => y.Name);//将源类型和目标类型的字段对应绑定起来 }); var info = TinyMapper.Map<EmployeeInfo>(add); Console.WriteLine(info.ToString());
复杂类型使用:类中类,类中List数组类,使用方式和上面一样,测试无问题
为null时,会报异常:Value cannot be null. 所以使用TinyMapper需要判断是否为null
效率:来源于其他人的测试
自定义1 > AutoMapper > TinyMapper