• Util应用框架基础(二) - 对象到对象映射(AutoMapper)


    本节介绍Util应用框架相似对象之间的转换方法.

    文章分为多个小节,如果对设计原理不感兴趣,只需阅读基础用法部分即可.

    概述

    现代化分层架构,普遍采用了构造块DTO(数据传输对象).

    DTO是一种参数对象,当Web API接收到请求,请求参数被装载到DTO对象中.

    我们需要把 DTO 对象转换成实体,才能保存到数据库.

    当返回响应消息时,需要把实体转换成DTO,再传回客户端.

    对于简单的系统,DTO和实体非常相似,它们可能包含大量相同的属性.

    除此之外,还有很多场景也需要转换相似对象.

    下面的例子定义了学生实体和学生参数DTO.

    它们包含两个相同的属性.

    StudentService 是一个应用服务.

    CreateAsync 方法创建学生,把DTO对象手工赋值转换为学生实体,并添加到数据库.

    GetByIdAsync 方法通过ID获取学生实体,并手工赋值转换为学生DTO.

    /// 
    /// 学生
    /// 
    public class Student : AggregateRoot {
        /// 
        /// 初始化学生
        /// 
        public Student() : this( Guid.Empty ) {
        }
    
        /// 
        /// 初始化学生
        /// 
        /// 学生标识
        public Student( Guid id ) : base( id ) {
        }
    
        /// 
        /// 姓名
        ///
        public string Name { get; set; }
    
        /// 
        /// 出生日期
        ///
        public DateTime? Birthday { get; set; }
    }
    
    /// 
    /// 学生参数
    /// 
    public class StudentDto : DtoBase {
        /// 
        /// 姓名
        ///
        public string Name { get; set; }
        /// 
        /// 出生日期
        ///
        public DateTime? Birthday { get; set; }
    }
    
    /// 
    /// 学生服务
    /// 
    public class StudentService {
        /// 
        /// 工作单元
        /// 
        private IDemoUnitOfWork _demoUnitOfWork;
        /// 
        /// 学生仓储
        /// 
        private IStudentRepository _repository;
    
        /// 
        /// 初始化学生服务
        /// 
        /// 工作单元
        /// 学生仓储
        public StudentService( IDemoUnitOfWork unitOfWork, IStudentRepository repository ) {
            _demoUnitOfWork = unitOfWork;
            _repository = repository;
        }
    
        /// 
        /// 创建学生
        /// 
        /// 学生参数
        public async Task CreateAsync( StudentDto dto ) {
            var entity = new Student { Name = dto.Name, Birthday = dto.Birthday };
            await _repository.AddAsync( entity );
            await _demoUnitOfWork.CommitAsync();
        }
    
        /// 
        /// 获取学生
        /// 
        /// 学生标识
        public async Task GetByIdAsync( Guid id ) {
            var entity = await _repository.FindByIdAsync( id );
            return new StudentDto { Name = entity.Name, Birthday = entity.Birthday };
        }
    }
    

    学生范例只有两个属性,手工转换工作量并不大.

    但真实的应用每个对象可能包含数十个属性,使用手工赋值的方式转换,效率低下且容易出错.

    我们需要一种自动化的转换手段.

    对象到对象映射框架 AutoMapper

    Util应用框架使用 AutoMapper ,它是 .Net 最流行的对象间映射框架.

    AutoMapper 可以自动转换相同名称和类型的属性,同时支持一些约定转换方式.

    基础用法

    引用Nuget包

    Nuget包名: Util.ObjectMapping.AutoMapper.

    通常不需要手工引用它.

    MapTo 扩展方法

    Util应用框架在根对象 object 扩展了 MapTo 方法,你可以在任何对象上调用 MapTo 进行对象转换.

    扩展方法需要引用命名空间, MapTo 扩展方法在 Util 命名空间.

    using Util;

    有两种调用形式.

    • 调用形式1: 源对象实例.MapTo<目标类型>()

      • 范例: 这里的源对象实例是学生参数 dto,目标类型是 Student,返回 Student 对象实例.
        /// 
        /// 创建学生
        /// 
        /// 学生参数
        public async Task CreateAsync( StudentDto dto ) {
            var entity = dto.MapTo();
            ...
        }
      
    • 调用形式2: 源对象实例.MapTo(目标类型实例)

      当目标类型实例已经存在时使用该重载.

      • 范例:
        /// 
        /// 创建学生
        /// 
        /// 学生参数
        public async Task CreateAsync( StudentDto dto ) {
            var entity = new Student();
            dto.MapTo(entity);
            ...
        }
      

    MapToList 扩展方法

    Util应用框架在 IEnumerable 扩展了 MapToList 方法.

    如果要转换集合,使用该扩展.

    范例

    将 StudentDto 集合转换为 Student 集合.

    传入泛型参数 Student ,而不是 List .

    List dtos = new List { new() { Name = "a" }, new() { Name = "b" } };
    List entities = dtos.MapToList();
    

    配置 AutoMapper

    对于简单场景,比如转换对象的属性都相同, 不需要任何配置.

    AutoMapper服务注册器自动完成基础配置.

    不过很多业务场景转换的对象具有差异,需要配置差异部分.

    Util.ObjectMapping.IAutoMapperConfig

    Util提供了 AutoMapper 配置接口 IAutoMapperConfig.

    /// 
    /// AutoMapper配置
    /// 
    public interface IAutoMapperConfig {
        /// 
        /// 配置映射
        /// 
        /// 配置映射表达式
        void Config( IMapperConfigurationExpression expression );
    }
    

    Config 配置方法提供配置映射表达式 IMapperConfigurationExpression 实例,它是 AutoMapper 配置入口.

    由 AutoMapper 服务注册器扫描执行所有 IAutoMapperConfig 配置.

    约定: 将 AutoMapper 配置类放置在 ObjectMapping 目录中.

    为每一对有差异的对象实现该接口.

    修改学生示例,把 StudentDto 的 Name 属性名改为 FullName.

    由于学生实体和DTO的Name属性名不同,所以不能自动转换,需要配置.

    需要配置两个映射方向.

    • 从 Student 到 StudentDto.

    • 从 StudentDto 到 Student.

    /// 
    /// 学生
    /// 
    public class Student : AggregateRoot {
        /// 
        /// 初始化学生
        /// 
        public Student() : this( Guid.Empty ) {
        }
    
        /// 
        /// 初始化学生
        /// 
        /// 学生标识
        public Student( Guid id ) : base( id ) {
        }
    
        /// 
        /// 姓名
        ///
        public string Name { get; set; }
    
        /// 
        /// 出生日期
        ///
        public DateTime? Birthday { get; set; }
    }
    
    /// 
    /// 学生参数
    /// 
    public class StudentDto : DtoBase {
        /// 
        /// 姓名
        ///
        public string FullName { get; set; }
        /// 
        /// 出生日期
        ///
        public DateTime? Birthday { get; set; }
    }
    
    /// 
    /// 学生映射配置
    /// 
    public class StudentAutoMapperConfig : IAutoMapperConfig {
        /// 
        /// 配置映射
        /// 
        /// 配置映射表达式
        public void Config( IMapperConfigurationExpression expression ) {
            expression.CreateMap()
                .ForMember( t => t.FullName, t => t.MapFrom( r => r.Name ) );
            expression.CreateMap()
                .ForMember( t => t.Name, t => t.MapFrom( r => r.FullName ) );
        }
    }
    

    对象间映射最佳实践

    应该尽量避免配置,保持代码简单.

    • 统一对象属性

      如果有可能,尽量统一对象属性名称和属性类型.

    • 使用 AutoMapper 映射约定

      AutoMapper 支持一些约定的映射方式.

      范例

      添加班级类型,学生实体添加班级关联实体 Class, 学生DTO添加班级名称属性 ClassName.

        /// 
        /// 学生
        /// 
        public class Student : AggregateRoot {
            /// 
            /// 初始化学生
            /// 
            public Student() : this( Guid.Empty ) {
            }
      
            /// 
            /// 初始化学生
            /// 
            /// 学生标识
            public Student( Guid id ) : base( id ) {
                Class = new Class();
            }
      
            /// 
            /// 姓名
            ///
            public string Name { get; set; }
      
            /// 
            /// 出生日期
            ///
            public DateTime? Birthday { get; set; }
      
            /// 
            /// 班级
            /// 
            public Class Class { get; set; }
        }
      
        /// 
        /// 班级
        /// 
        public class Class : AggregateRoot {
            /// 
            /// 初始化班级
            /// 
            public Class() : this( Guid.Empty ) {
            }
      
            /// 
            /// 初始化班级
            /// 
            /// 班级标识
            public Class( Guid id ) : base( id ) {
            }
      
            /// 
            /// 班级名称
            ///
            public string Name { get; set; }
        }
      
        /// 
        /// 学生参数
        /// 
        public class StudentDto : DtoBase {
            /// 
            /// 姓名
            ///
            public string Name { get; set; }
            /// 
            /// 班级名称
            ///
            public string ClassName { get; set; }
            /// 
            /// 出生日期
            ///
            public DateTime? Birthday { get; set; }
        }
      

      将 Student 的 Class实体 Name 属性映射到 StudentDto 的 ClassName 属性 ,不需要配置.

      var entity = new Student { Class = new Class { Name = "a" } };
      var dto = entity.MapTo();
      //dto.ClassName 值为 a
      

      但不支持从 StudentDto 的 ClassName 属性映射到 Student 的 Class实体 Name 属性.

      var dto = new StudentDto { ClassName = "a" };
      var entity = dto.MapTo();
      //entity.Class.Name 值为 null
      

    源码解析

    对象映射器 IObjectMapper

    你不需要调用 IObjectMapper 接口,始终通过 MapTo 扩展方法进行转换.

    ObjectMapper 实现了 IObjectMapper 接口.

    ObjectMapper映射源类型和目标类型时,如果发现尚未配置映射关系,则自动配置.

    除了自动配置映射关系外,还需要处理并发和异常情况.

    /// 
    /// 对象映射器
    /// 
    public interface IObjectMapper {
        /// 
        /// 将源对象映射到目标对象
        /// 
        /// 源类型
        /// 目标类型
        /// 源对象
        TDestination Map( TSource source );
        /// 
        /// 将源对象映射到目标对象
        /// 
        /// 源类型
        /// 目标类型
        /// 源对象
        /// 目标对象
        TDestination Map( TSource source, TDestination destination );
    }
    
    /// 
    /// AutoMapper对象映射器
    /// 
    public class ObjectMapper : IObjectMapper {
        /// 
        /// 最大递归获取结果次数
        /// 
        private const int MaxGetResultCount = 16;
        /// 
        /// 同步锁
        /// 
        private static readonly object Sync = new();
        /// 
        /// 配置表达式
        /// 
        private readonly MapperConfigurationExpression _configExpression;
        /// 
        /// 配置提供器
        /// 
        private IConfigurationProvider _config;
        /// 
        /// 对象映射器
        /// 
        private IMapper _mapper;
    
        /// 
        /// 初始化AutoMapper对象映射器
        /// 
        /// 配置表达式
        public ObjectMapper( MapperConfigurationExpression expression ) {
            _configExpression = expression ?? throw new ArgumentNullException( nameof( expression ) );
            _config = new MapperConfiguration( expression ); 
            _mapper = _config.CreateMapper();
        }
    
        /// 
        /// 将源对象映射到目标对象
        /// 
        /// 源类型
        /// 目标类型
        /// 源对象
        public TDestination Map( TSource source ) {
            return Map( source, default );
        }
    
        /// 
        /// 将源对象映射到目标对象
        /// 
        /// 源类型
        /// 目标类型
        /// 源对象
        /// 目标对象
        public TDestination Map( TSource source, TDestination destination ) {
            if ( source == null )
                return default;
            var sourceType = GetType( source );
            var destinationType = GetType( destination );
            return GetResult( sourceType, destinationType, source, destination,0 );
        }
    
        /// 
        /// 获取类型
        /// 
        private Type GetType( T obj ) {
            if( obj == null )
                return GetType( typeof( T ) );
            return GetType( obj.GetType() );
        }
    
        /// 
        /// 获取类型
        /// 
        private Type GetType( Type type ) {
            return Reflection.GetElementType( type );
        }
    
        /// 
        /// 获取结果
        /// 
        private TDestination GetResult( Type sourceType, Type destinationType, object source, TDestination destination,int i ) {
            try {
                if ( i >= MaxGetResultCount )
                    return default;
                i += 1;
                if ( Exists( sourceType, destinationType ) )
                    return GetResult( source, destination );
                lock ( Sync ) {
                    if ( Exists( sourceType, destinationType ) )
                        return GetResult( source, destination );
                    ConfigMap( sourceType, destinationType );
                }
                return GetResult( source, destination );
            }
            catch ( AutoMapperMappingException ex ) {
                if ( ex.InnerException != null && ex.InnerException.Message.StartsWith( "Missing type map configuration" ) )
                    return GetResult( GetType( ex.MemberMap.SourceType ), GetType( ex.MemberMap.DestinationType ), source, destination,i );
                throw;
            }
        }
    
        /// 
        /// 是否已存在映射配置
        /// 
        private bool Exists( Type sourceType, Type destinationType ) {
            return _config.Internal().FindTypeMapFor( sourceType, destinationType ) != null;
        }
    
        /// 
        /// 获取映射结果
        /// 
        private TDestination GetResult( TSource source, TDestination destination ) {
            return _mapper.Map( source, destination );
        }
    
        /// 
        /// 动态配置映射
        /// 
        private void ConfigMap( Type sourceType, Type destinationType ) {
            _configExpression.CreateMap( sourceType, destinationType );
            _config = new MapperConfiguration( _configExpression );
            _mapper = _config.CreateMapper();
        }
    }
    

    AutoMapper服务注册器

    AutoMapper服务注册器扫描 IAutoMapperConfig 配置并执行.

    同时为 MapTo 扩展类 ObjectMapperExtensions 设置 IObjectMapper 实例.

    /// 
    /// AutoMapper服务注册器
    /// 
    public class AutoMapperServiceRegistrar : IServiceRegistrar {
        /// 
        /// 获取服务名
        /// 
        public static string ServiceName => "Util.ObjectMapping.Infrastructure.AutoMapperServiceRegistrar";
    
        /// 
        /// 排序号
        /// 
        public int OrderId => 300;
    
        /// 
        /// 是否启用
        /// 
        public bool Enabled => ServiceRegistrarConfig.IsEnabled( ServiceName );
    
        /// 
        /// 注册服务
        /// 
        /// 服务上下文
        public Action Register( ServiceContext serviceContext ) {
            var types = serviceContext.TypeFinder.Find();
            var instances = types.Select( type => Reflection.CreateInstance( type ) ).ToList();
            var expression = new MapperConfigurationExpression();
            instances.ForEach( t => t.Config( expression ) );
            var mapper = new ObjectMapper( expression );
            ObjectMapperExtensions.SetMapper( mapper );
            serviceContext.HostBuilder.ConfigureServices( ( context, services ) => {
                services.AddSingleton( mapper );
            } );
            return null;
        }
    }
    

    对象映射扩展 ObjectMapperExtensions

    ObjectMapperExtensions 提供了 MapToMapToList 扩展方法.

    MapTo 扩展方法依赖 IObjectMapper 实例,由于扩展方法是静态方法,所以需要将 IObjectMapper 定义为静态变量.

    通过 SetMapper 静态方法将对象映射器实例传入.

    对象映射器 ObjectMapper 实例作为静态变量,必须处理并发相关的问题.

    /// 
    /// 对象映射扩展
    /// 
    public static class ObjectMapperExtensions {
        /// 
        /// 对象映射器
        /// 
        private static IObjectMapper _mapper;
    
        /// 
        /// 设置对象映射器
        /// 
        /// 对象映射器
        public static void SetMapper( IObjectMapper mapper ) {
            _mapper = mapper ?? throw new ArgumentNullException( nameof( mapper ) );
        }
    
        /// 
        /// 将源对象映射到目标对象
        /// 
        /// 目标类型
        /// 源对象
        public static TDestination MapTo( this object source ) {
            if ( _mapper == null )
                throw new ArgumentNullException( nameof(_mapper) );
            return _mapper.Map( source );
        }
            
        /// 
        /// 将源对象映射到目标对象
        /// 
        /// 源类型
        /// 目标类型
        /// 源对象
        /// 目标对象
        public static TDestination MapTo( this TSource source, TDestination destination ) {
            if( _mapper == null )
                throw new ArgumentNullException( nameof( _mapper ) );
            return _mapper.Map( source, destination );
        }
    
        /// 
        /// 将源集合映射到目标集合
        /// 
        /// 目标元素类型,范例:Sample,不要加List
        /// 源集合
        public static List MapToList( this System.Collections.IEnumerable source ) {
            return MapTo>( source );
        }
    }
    

    禁用 AutoMapper 服务注册器

    ServiceRegistrarConfig.Instance.DisableAutoMapperServiceRegistrar();
    
  • 相关阅读:
    Java计算机毕业设计视频网站的设计与实现源码+系统+数据库+lw文档
    Linux网络配置管理和设置
    数据可视化之百变柱状图
    Mac navicat连接mysql出现1045 - Access denied for user ‘root‘
    高级java每日一道面试题-2024年8月10日-网络篇-websocket应用的是哪个协议?
    基于Python实现可视化分析中国500强排行榜数据的设计与实现
    c++中操作符->与 . 的使用与区别
    5.2 Redis面试题
    day36 XSS跨站&MXSS&UXSS&FlashXSS&PDFXSS
    NCV1117ST50T3G线性稳压器芯片中文资料规格书PDF数据手册引脚图图片价格参数
  • 原文地址:https://www.cnblogs.com/xiadao521/p/17807427.html