PAYATHON 2026

Mapster 的 Adapt<T>() 会应用 IRegister 自定义映射吗?

数据阿明

不需要使用 IMapper 也能应用 IRegister 中的自定义映射。source.Adapt<T>() 默认读取 TypeAdapterConfig.GlobalSettings,会使用已应用到全局配置的规则。Mapster 的 TypeAdapter 源码

如果规则只注册在独立的 TypeAdapterConfig 实例中,就需要调用 source.Adapt<T>(config),或者使用基于该实例创建的 mapper。仅凭两种写法返回相同结果,无法判断自定义映射是否生效。

IRegister 的规则何时生效?

IRegister 用来组织映射规则。定义实现类后,还需要将规则应用到某个配置实例,例如调用该实例的 Scan(...) 或 Apply(...)。普通的 Adapt<T>() 调用不会自行扫描程序集,也不会从依赖注入容器中寻找 IRegister。Mapster 配置注册文档

不同调用方式使用的配置如下:

调用方式使用的配置
source.Adapt<ResultDTO>()TypeAdapterConfig.GlobalSettings
source.Adapt<ResultDTO>(config)显式传入的 config
new MapsterMapper.Mapper(config).Map<ResultDTO>(source)构造时传入的 config
_mapper.Map<ResultDTO>(source)被注入 mapper 实例实际持有的配置

Mapster 自带的 Mapper 无参构造函数使用全局配置;传入配置的构造函数会保存该实例,并在映射时通过它获取映射函数。因此,使用 IMapper 并不意味着它一定读取全局配置。Mapster 的 Mapper 源码

使用全局配置时如何注册?

下面使用独立的示例类型演示自定义字段映射,不假设原问题中的 Result 和 ResultDTO 具有哪些属性:

using Mapster;

public class Person
{
    public string Name { get; set; } = "";
}

public class PersonDto
{
    public string DisplayName { get; set; } = "";
}

public class PersonRegister : IRegister
{
    public void Register(TypeAdapterConfig config)
    {
        config.NewConfig<Person, PersonDto>()
            .Map(dest => dest.DisplayName, src => src.Name);
    }
}

在应用启动阶段,将规则扫描到全局配置:

TypeAdapterConfig.GlobalSettings.Scan(
    typeof(PersonRegister).Assembly);

之后直接调用 Adapt<T>() 就会使用该规则:

var person = new Person { Name = "Ada" };
var dto = person.Adapt<PersonDto>();

// 按上述规则,dto.DisplayName 为 "Ada"。

这里通过 typeof(PersonRegister).Assembly 指定包含注册类的程序集。如果规则分布在多个程序集中,需要扫描相应程序集。也可以显式应用某个注册类:

TypeAdapterConfig.GlobalSettings.Apply(new PersonRegister());

Scan 和 Apply 是两种注册方式,通常选一种即可。Mapster 配置注册文档

使用独立配置时如何调用?

如果应用为不同场景维护独立配置,应复用对应实例,并在调用时明确使用它:

using Mapster;
using MapsterMapper;

var config = new TypeAdapterConfig();
config.Scan(typeof(PersonRegister).Assembly);

var person = new Person { Name = "Ada" };

var dto1 = person.Adapt<PersonDto>(config);

var mapper = new Mapper(config);
var dto2 = mapper.Map<PersonDto>(person);

这两次映射使用相同的 config。如果改为 person.Adapt<PersonDto>(),读取的仍是全局配置;局部变量名为 config,或者它已注册到 DI 容器,都不会改变这一点。Mapster 文档支持将配置实例传给 Adapt 或 Mapper。Mapster 独立配置文档

对于原问题中的仓储代码,应检查应用启动时如何创建和注册 IMapper。如果规则扫描到了独立配置,且注入的 mapper 使用该配置,_mapper.Map<ResultDTO>(res) 就能读取这些规则;无配置参数的 res.Adapt<ResultDTO>() 仍然读取全局配置。

为什么两个结果一样?

可能是 mapper 使用了全局配置,也可能是当前数据只涉及默认约定即可完成的映射,或者自定义规则对本次输入没有造成可见差异。原问题没有提供 IRegister 内容和 DI 注册代码,因此无法确定具体原因。

可以选择一个必须依赖自定义规则才能得到的字段,例如上例中的 Name → DisplayName,分别检查全局配置调用、显式配置调用和 mapper 调用的结果。只比较同名字段,通常不足以证明自定义规则已经执行。

还有哪些注意事项?

  • 在首次执行映射前完成配置注册,并复用配置实例,避免在每次请求中重新扫描或创建配置。Mapster 配置生命周期说明
  • 如果映射表达式通过 MapContext.Current.GetService<T>() 解析 DI 服务,应按依赖注入文档使用 ServiceMapper 执行映射。直接传入配置的 Adapt<T>(config) 本身不会建立相同的服务解析上下文。Mapster 依赖注入文档
  • 从 AutoMapper 迁移时,确认注入的接口是 MapsterMapper.IMapper,避免与 AutoMapper.IMapper 混淆。
  • 原代码的 FirstOrDefaultAsync() 可能查不到记录,需要明确空结果的处理方式;当前查询也没有使用传入的 id。这两点应单独处理,不影响配置来源的判断。

备注:内容仅供参考。