跳到主要内容
版本:3.0.0

对象映射(Mapster)

解决什么问题

分层架构中,不同层使用不同的数据类:数据库实体(Entity)、数据传输对象(Dto)、请求对象(Request)。层与层之间需要频繁做属性拷贝。手动写映射代码枯燥且容易遗漏字段。

Aegis 集成了 Mapster,通过约定自动完成属性映射,不需要逐字段赋值。同时支持自定义映射规则来处理属性名不一致、类型转换等场景。

如何引入

不需要单独安装或注册。Mapster 已包含在 Aegis.Core.Service 中,在 Component.deps.jsonServices 中添加 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 的映射基于属性名约定,大小写不敏感