本节介绍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 { Clas服务器托管网s = 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 提供了 MapTo 和 MapToList 扩展方法.
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
禁用 AutoMapper 服务注册器
ServiceRegistrarConfig.Instance.DisableAutoMapperServiceRegistrar();
服务器托管,北京服务器托管,服务器租用 http://www.fwqtg.net
机房租用,北京机房租用,IDC机房托管, http://www.fwqtg.net