对象映射(Mapster)
解决什么问题
分层架构中,不同层使用不同的数据类:数据库实体(Entity)、数据传输对象(Dto)、请求对象(Request)。层与层之间需要频繁做属性拷贝。手动写映射代码枯燥且容易遗漏字段。
Aegis 集成了 Mapster,通过约定自动完成属性映射,不需要逐字段赋值。同时支持自定义映射规则来处理属性名不一致、类型转换等场景。
如何引入
不需要单独安装或注册。Mapster 已包含在 Aegis.Core.Service 中,在 Component.deps.json 的 Services 中添加 BusinessServices 即可:
{
"Components": {
"Services": [
"BusinessServices"
]
}
}
BusinessServices 组件会在启动时自动扫描 *.Contract.dll 和 *.Services.dll 中的映射配置类。
Adapt 直接映射
Mapster 的核心用法是 .Adapt<T>() 扩展方法。当源对象和目标对象的属性名一致时,不需要任何配置:
using Mapster;
// 单个对象
var dto = entity.Adapt<UserDto>();
// 集合
var dtos = entities.Adapt<List<UserDto>>();
// 映射到已有对象
entity.Adapt(existingDto);
属性名约定
Mapster 默认按属性名匹配,大小写不敏感。以下两个类可以直接映射:
public class User
{
public string Name { get; set; }
public int Age { get; set; }
public string Email { get; set; }
}
public record UserDto
{
public string Name { get; init; }
public int Age { get; init; }
public string Email { get; init; }
}
源对象有多余属性时自动忽略,目标对象缺少对应属性时不赋值。
DtoPaged 分页映射
框架内置了 DtoPaged<TDto> 工具类,直接将数据库分页结果映射为 Dto 分页结果:
var pagedList = await _repository.GetPagedListAsync(request);
var result = DtoPaged<UserDto>.Convert<User>(pagedList);
不需要手动遍历映射。
自定义映射配置
当属性名不一致或需要特殊转换时,创建一个实现 IRegister 接口的配置类。
配置类的写法
在 *.Services 项目的 MapperRegisters 目录下创建:
using Mapster;
namespace Aegis.Webapi.Services.MapperRegisters;
internal class UserDtoRegister : IRegister
{
public void Register(TypeAdapterConfig config)
{
config.ForType<UserDto2, UserDto>()
.IgnoreNullValues(true);
}
}
框架会在启动时自动扫描所有实现了 IRegister 的类并应用配置。
常用配置
映射指定字段:属性名不一致时手动指定:
config.ForType<CreateUserRequest, User>()
.Map(dest => dest.PhoneNumber, src => src.Phone);
忽略空值:源属性为 null 时不覆盖目标属性:
config.ForType<UserDto2, UserDto>()
.IgnoreNullValues(true);
忽略字段:跳过不需要映射的属性:
config.ForType<UserDto, User>()
.Ignore(dest => dest.PasswordHash)
.Ignore(dest => dest.CreateTime);
自定义转换逻辑:
config.ForType<User, UserDto>()
.Map(dest => dest.DisplayName, src => src.FirstName + " " + src.LastName);
config.ForType<CreateOrderRequest, Order>()
.Map(dest => dest.Amount, src => Math.Round(src.Amount, 2));
条件映射:只在满足条件时映射某个字段:
config.ForType<UpdateUserRequest, User>()
.Map(dest => dest.Status, src => src.Status, srcMap => srcMap.Status != null);
多个配置类的组织
不同业务域的映射配置可以拆分到不同的类中:
MapperRegisters/
├── UserDtoRegister.cs
├── OrderDtoRegister.cs
└── PatientDtoRegister.cs
框架会扫描并应用所有 IRegister 实现类。
使用建议
- 同名属性直接用
.Adapt<T>(),不需要配置 - 只有属性名不一致或需要转换时才写
IRegister - 映射配置类放在
*.Services项目中 - 不要在 Controller 中做映射,映射是 Service 层的职责
- Entity → Dto 是最常见的方向,避免反向映射(Dto → Entity)导致意外覆盖数据库字段
边界与限制
- 映射配置类必须实现
IRegister接口,否则不会被扫描到 - 映射配置类必须放在
*.Contract.dll或*.Services.dll对应的项目中 - Mapster 的映射基于属性名约定,大小写不敏感