首页 > 其他分享 >Util应用框架基础(二) - 对象到对象映射(AutoMapper)

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

时间:2023-11-03 13:44:58浏览次数:38  
标签:Name 映射 对象 Util source AutoMapper public

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

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

概述

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

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

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

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

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

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

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

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

StudentService 是一个应用服务.

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

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

/// <summary>
/// 学生
/// </summary>
public class Student : AggregateRoot<Student> {
    /// <summary>
    /// 初始化学生
    /// </summary>
    public Student() : this( Guid.Empty ) {
    }

    /// <summary>
    /// 初始化学生
    /// </summary>
    /// <param name="id">学生标识</param>
    public Student( Guid id ) : base( id ) {
    }

    /// <summary>
    /// 姓名
    ///</summary>
    public string Name { get; set; }

    /// <summary>
    /// 出生日期
    ///</summary>
    public DateTime? Birthday { get; set; }
}

/// <summary>
/// 学生参数
/// </summary>
public class StudentDto : DtoBase {
    /// <summary>
    /// 姓名
    ///</summary>
    public string Name { get; set; }
    /// <summary>
    /// 出生日期
    ///</summary>
    public DateTime? Birthday { get; set; }
}

/// <summary>
/// 学生服务
/// </summary>
public class StudentService {
    /// <summary>
    /// 工作单元
    /// </summary>
    private IDemoUnitOfWork _demoUnitOfWork;
    /// <summary>
    /// 学生仓储
    /// </summary>
    private IStudentRepository _repository;

    /// <summary>
    /// 初始化学生服务
    /// </summary>
    /// <param name="unitOfWork">工作单元</param>
    /// <param name="repository">学生仓储</param>
    public StudentService( IDemoUnitOfWork unitOfWork, IStudentRepository repository ) {
        _demoUnitOfWork = unitOfWork;
        _repository = repository;
    }

    /// <summary>
    /// 创建学生
    /// </summary>
    /// <param name="dto">学生参数</param>
    public async Task CreateAsync( StudentDto dto ) {
        var entity = new Student { Name = dto.Name, Birthday = dto.Birthday };
        await _repository.AddAsync( entity );
        await _demoUnitOfWork.CommitAsync();
    }

    /// <summary>
    /// 获取学生
    /// </summary>
    /// <param name="id">学生标识</param>
    public async Task<StudentDto> 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 对象实例.
      /// <summary>
      /// 创建学生
      /// </summary>
      /// <param name="dto">学生参数</param>
      public async Task CreateAsync( StudentDto dto ) {
          var entity = dto.MapTo<Student>();
          ...
      }
    
  • 调用形式2: 源对象实例.MapTo(目标类型实例)

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

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

MapToList 扩展方法

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

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

范例

将 StudentDto 集合转换为 Student 集合.

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

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

配置 AutoMapper

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

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

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

Util.ObjectMapping.IAutoMapperConfig

Util提供了 AutoMapper 配置接口 IAutoMapperConfig.

/// <summary>
/// AutoMapper配置
/// </summary>
public interface IAutoMapperConfig {
    /// <summary>
    /// 配置映射
    /// </summary>
    /// <param name="expression">配置映射表达式</param>
    void Config( IMapperConfigurationExpression expression );
}

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

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

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

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

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

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

需要配置两个映射方向.

  • 从 Student 到 StudentDto.

  • 从 StudentDto 到 Student.

/// <summary>
/// 学生
/// </summary>
public class Student : AggregateRoot<Student> {
    /// <summary>
    /// 初始化学生
    /// </summary>
    public Student() : this( Guid.Empty ) {
    }

    /// <summary>
    /// 初始化学生
    /// </summary>
    /// <param name="id">学生标识</param>
    public Student( Guid id ) : base( id ) {
    }

    /// <summary>
    /// 姓名
    ///</summary>
    public string Name { get; set; }

    /// <summary>
    /// 出生日期
    ///</summary>
    public DateTime? Birthday { get; set; }
}

/// <summary>
/// 学生参数
/// </summary>
public class StudentDto : DtoBase {
    /// <summary>
    /// 姓名
    ///</summary>
    public string FullName { get; set; }
    /// <summary>
    /// 出生日期
    ///</summary>
    public DateTime? Birthday { get; set; }
}

/// <summary>
/// 学生映射配置
/// </summary>
public class StudentAutoMapperConfig : IAutoMapperConfig {
    /// <summary>
    /// 配置映射
    /// </summary>
    /// <param name="expression">配置映射表达式</param>
    public void Config( IMapperConfigurationExpression expression ) {
        expression.CreateMap<Student, StudentDto>()
            .ForMember( t => t.FullName, t => t.MapFrom( r => r.Name ) );
        expression.CreateMap<StudentDto,Student>()
            .ForMember( t => t.Name, t => t.MapFrom( r => r.FullName ) );
    }
}

对象间映射最佳实践

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

  • 统一对象属性

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

  • 使用 AutoMapper 映射约定

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

    范例

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

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

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

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

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

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

源码解析

对象映射器 IObjectMapper

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

ObjectMapper 实现了 IObjectMapper 接口.

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

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

/// <summary>
/// 对象映射器
/// </summary>
public interface IObjectMapper {
    /// <summary>
    /// 将源对象映射到目标对象
    /// </summary>
    /// <typeparam name="TSource">源类型</typeparam>
    /// <typeparam name="TDestination">目标类型</typeparam>
    /// <param name="source">源对象</param>
    TDestination Map<TSource, TDestination>( TSource source );
    /// <summary>
    /// 将源对象映射到目标对象
    /// </summary>
    /// <typeparam name="TSource">源类型</typeparam>
    /// <typeparam name="TDestination">目标类型</typeparam>
    /// <param name="source">源对象</param>
    /// <param name="destination">目标对象</param>
    TDestination Map<TSource, TDestination>( TSource source, TDestination destination );
}

/// <summary>
/// AutoMapper对象映射器
/// </summary>
public class ObjectMapper : IObjectMapper {
    /// <summary>
    /// 最大递归获取结果次数
    /// </summary>
    private const int MaxGetResultCount = 16;
    /// <summary>
    /// 同步锁
    /// </summary>
    private static readonly object Sync = new();
    /// <summary>
    /// 配置表达式
    /// </summary>
    private readonly MapperConfigurationExpression _configExpression;
    /// <summary>
    /// 配置提供器
    /// </summary>
    private IConfigurationProvider _config;
    /// <summary>
    /// 对象映射器
    /// </summary>
    private IMapper _mapper;

    /// <summary>
    /// 初始化AutoMapper对象映射器
    /// </summary>
    /// <param name="expression">配置表达式</param>
    public ObjectMapper( MapperConfigurationExpression expression ) {
        _configExpression = expression ?? throw new ArgumentNullException( nameof( expression ) );
        _config = new MapperConfiguration( expression ); 
        _mapper = _config.CreateMapper();
    }

    /// <summary>
    /// 将源对象映射到目标对象
    /// </summary>
    /// <typeparam name="TSource">源类型</typeparam>
    /// <typeparam name="TDestination">目标类型</typeparam>
    /// <param name="source">源对象</param>
    public TDestination Map<TSource, TDestination>( TSource source ) {
        return Map<TSource, TDestination>( source, default );
    }

    /// <summary>
    /// 将源对象映射到目标对象
    /// </summary>
    /// <typeparam name="TSource">源类型</typeparam>
    /// <typeparam name="TDestination">目标类型</typeparam>
    /// <param name="source">源对象</param>
    /// <param name="destination">目标对象</param>
    public TDestination Map<TSource, TDestination>( TSource source, TDestination destination ) {
        if ( source == null )
            return default;
        var sourceType = GetType( source );
        var destinationType = GetType( destination );
        return GetResult( sourceType, destinationType, source, destination,0 );
    }

    /// <summary>
    /// 获取类型
    /// </summary>
    private Type GetType<T>( T obj ) {
        if( obj == null )
            return GetType( typeof( T ) );
        return GetType( obj.GetType() );
    }

    /// <summary>
    /// 获取类型
    /// </summary>
    private Type GetType( Type type ) {
        return Reflection.GetElementType( type );
    }

    /// <summary>
    /// 获取结果
    /// </summary>
    private TDestination GetResult<TDestination>( 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;
        }
    }

    /// <summary>
    /// 是否已存在映射配置
    /// </summary>
    private bool Exists( Type sourceType, Type destinationType ) {
        return _config.Internal().FindTypeMapFor( sourceType, destinationType ) != null;
    }

    /// <summary>
    /// 获取映射结果
    /// </summary>
    private TDestination GetResult<TSource, TDestination>( TSource source, TDestination destination ) {
        return _mapper.Map( source, destination );
    }

    /// <summary>
    /// 动态配置映射
    /// </summary>
    private void ConfigMap( Type sourceType, Type destinationType ) {
        _configExpression.CreateMap( sourceType, destinationType );
        _config = new MapperConfiguration( _configExpression );
        _mapper = _config.CreateMapper();
    }
}

AutoMapper服务注册器

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

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

/// <summary>
/// AutoMapper服务注册器
/// </summary>
public class AutoMapperServiceRegistrar : IServiceRegistrar {
    /// <summary>
    /// 获取服务名
    /// </summary>
    public static string ServiceName => "Util.ObjectMapping.Infrastructure.AutoMapperServiceRegistrar";

    /// <summary>
    /// 排序号
    /// </summary>
    public int OrderId => 300;

    /// <summary>
    /// 是否启用
    /// </summary>
    public bool Enabled => ServiceRegistrarConfig.IsEnabled( ServiceName );

    /// <summary>
    /// 注册服务
    /// </summary>
    /// <param name="serviceContext">服务上下文</param>
    public Action Register( ServiceContext serviceContext ) {
        var types = serviceContext.TypeFinder.Find<IAutoMapperConfig>();
        var instances = types.Select( type => Reflection.CreateInstance<IAutoMapperConfig>( 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<IObjectMapper>( mapper );
        } );
        return null;
    }
}

对象映射扩展 ObjectMapperExtensions

ObjectMapperExtensions 提供了 MapToMapToList 扩展方法.

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

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

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

/// <summary>
/// 对象映射扩展
/// </summary>
public static class ObjectMapperExtensions {
    /// <summary>
    /// 对象映射器
    /// </summary>
    private static IObjectMapper _mapper;

    /// <summary>
    /// 设置对象映射器
    /// </summary>
    /// <param name="mapper">对象映射器</param>
    public static void SetMapper( IObjectMapper mapper ) {
        _mapper = mapper ?? throw new ArgumentNullException( nameof( mapper ) );
    }

    /// <summary>
    /// 将源对象映射到目标对象
    /// </summary>
    /// <typeparam name="TDestination">目标类型</typeparam>
    /// <param name="source">源对象</param>
    public static TDestination MapTo<TDestination>( this object source ) {
        if ( _mapper == null )
            throw new ArgumentNullException( nameof(_mapper) );
        return _mapper.Map<object, TDestination>( source );
    }
        
    /// <summary>
    /// 将源对象映射到目标对象
    /// </summary>
    /// <typeparam name="TSource">源类型</typeparam>
    /// <typeparam name="TDestination">目标类型</typeparam>
    /// <param name="source">源对象</param>
    /// <param name="destination">目标对象</param>
    public static TDestination MapTo<TSource, TDestination>( this TSource source, TDestination destination ) {
        if( _mapper == null )
            throw new ArgumentNullException( nameof( _mapper ) );
        return _mapper.Map( source, destination );
    }

    /// <summary>
    /// 将源集合映射到目标集合
    /// </summary>
    /// <typeparam name="TDestination">目标元素类型,范例:Sample,不要加List</typeparam>
    /// <param name="source">源集合</param>
    public static List<TDestination> MapToList<TDestination>( this System.Collections.IEnumerable source ) {
        return MapTo<List<TDestination>>( source );
    }
}

禁用 AutoMapper 服务注册器

ServiceRegistrarConfig.Instance.DisableAutoMapperServiceRegistrar();

标签:Name,映射,对象,Util,source,AutoMapper,public
From: https://www.cnblogs.com/xiadao521/p/17807427.html

相关文章

  • 实现一个极简的字节数组对象池
    .NET利用ArrayPoolPool<T>和MemoryPool<T>提供了针对Array/Memory<T>的对象池功能。最近在一个项目中需要使用到针对字节数组的对象池,由于这些池化的字节数组相当庞大,我希望将它们分配到POH上以降低GC的压力。由于ArrayPoolPool<T>没法提供支持,所以我提供了一个极简的实现。目录一......
  • JavaScript String对象及方法总结
    String对象创建方法: newString()vartxt1=newString("string");vartxt2="string";String对象属性1、constructor:返回对String对象属性创建的函数  返回值:函数的引用,不是函数名:    字符串constructor属性返回 functionString(){[nativecode]}2、......
  • Spring,IOC创建对象的方式,无参有参
    创建一个spring模块,创建有无参构造的User实体类  方式一、无参构造创建对象  (默认的)我们知道:创建对象是调用了实体类中的构造方法的Spring这边通过配置文件也是默认调用了无参构造 二、有参构造创建对象用法1、通过下标赋值  index=“0” 因为User中就一个nam......
  • .NET6 配置 AutoMapper 与 AutoFac
    AutoMapper 概述1、什么是AutoMapper简单来说,AutoMapper就是一个用C#语言开发的一个轻量的处理一个实体对象到另外一个实体对象映射关系的组件库 官网地址:AutoMapper 文档地址:AutoMapper—AutoMapperdocumentation 2、为什么要用AutoMapper层与层之间的数据转换......
  • Util应用框架基础(一) - 依赖注入
    本节介绍Util应用框架依赖注入的使用和配置扩展.文章分为多个小节,如果对设计原理不感兴趣,只需阅读基础用法部分即可.概述当你想调用某个服务的方法完成特定功能时,首先需要得到这个服务的实例.最简单的办法是直接new一个服务实例,不过这样就把服务的实现牢牢绑死了,当你需......
  • ElementUI Checkbox 多选框 返回对象
    checkBox和checkGroup通过v-model绑定的数据只能是number/string/Array 如何回调返回对象呢? 已知能返回label字符串,我们可以把label=对象id +','+ 对象名称拼接,然后返回,或者label=json字符串再传出每次选中操作后会回调返回选中数组 ......
  • .NET6 使用AutoMapper
    .NET6使用AutoMapper 一、Net6环境下的.netcore项目里如何使用AutoMapper实现依赖注入。注:AutoMapper是一个对象-对象映射器,可以将一个对象映射到另一个对象。第一步,在Nuget引入AutoMapper、AutoMapper.Extensions.DependencyInjection这两个NuGet包  ......
  • 五、Java面向对象:多态
    一、多态介绍概述:顾名思义就是事物在某种时刻的多种状态前提;1、要有继承关系2、要有方法的重写3、要有父类的引用指向子类对象 访问成员的特点:访问成员变量:编译看左,运行看左访问成员方法:编译看左,运行看右访......
  • C++类&对象
    C++类&对象C++在C语言的基础上增加了面向对象编程,C++支持面向对象程序设计。类是C++的核心特性,通常被称为用户定义的类型。类用于指定对象的形式,它包含了数据表示法和用于处理数据的方法。类中的数据和方法称为类的成员。函数在一个类中被称为类的成员。C++类定义定义一个类,......
  • java new(创建)对象时结尾带上{}和不带的区别
    定义一个对象publicclassPerson{publicvoidsay(){System.out.println("hello");}}熟悉(正常)的创建对象方式Personp1=newPerson();不熟悉的创建方式Personp2=newPerson(){};那二者有什么区别?我们可以先打印出类的信息看看public......