.net5 - 创建Web.Api项目(七)对象关系映射

方式一:自定义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

 

posted @ 2021-03-06 15:11  gygtech  Views(256)  Comments(0Edit  收藏  举报