Mapster 对象映射实战:比 AutoMapper 更快、更轻量的 C# 映射方案
在日常的 .NET 开发里,“实体转 DTO、DTO 转实体、VO 转 PO”
这类对象映射代码写起来枯燥又容易出错。传统做法不外乎手动
new 一个个属性赋值,或者用 AutoMapper
这类重型库。前者维护困难,后者虽然功能强大,但反射开销和配置复杂度往往让人望而却步。
最近在维护 SwitchData 项目时,我发现代码里大量使用了 Mapster 来处理菜单、用户、网元等模块的对象转换。相比 AutoMapper,Mapster 的编译时映射生成机制让它在性能和易用性之间取得了很好的平衡。本文就结合项目源码,聊一聊 Mapster 的核心用法和实战技巧。
一、Mapster 是什么
Mapster 是一个高性能、轻量级的 .NET 对象映射库,支持:
- 编译时映射:默认生成基于表达式的映射代码,运行时无需反射,速度接近手写代码。
- 灵活配置:支持简单字段映射、自定义转换、嵌套对象、集合映射、双向映射等。
- 与现有代码共存:既可以全局配置
TypeAdapterConfig,也可以局部用.Adapt<T>()直接映射。
对于中小型项目或者对性能敏感的场景,Mapster 是一个很好的选择。
二、在项目中初始化 Mapster
Mapster 的配置通常放在程序启动时一次性注册。在 SwitchData.Api 的
Program.cs 中,可以看到这样一行:
// 6. 对象映射
MapsterConfig.Configure();
对应的 MapsterConfig.cs
是一个静态类,里面集中管理了所有映射规则:
public static class MapsterConfig
{
public static void Configure()
{
// VueMenu -> MenuTree
TypeAdapterConfig<VueMenu, MenuTree>
.NewConfig()
.AfterMapping((src, dest) =>
{
dest.children = new List<MenuTree>();
dest.component = src.Component.ToString();
dest.hidden = !src.Visible;
dest.meta = new MenuMeta
{
icon = src.Icon,
link = src.IsFrame ? src.Path : null,
title = src.Text,
noCache = !src.IsCache
};
dest.name = src.Path.ToString();
dest.id = src.Id.ToString();
dest.pid = src.ParentId.ToString();
dest.alwaysShow = src.MenuType == "M";
dest.path = src.Path;
});
}
}
这种方式的好处是:映射规则集中、便于维护,新业务上线时只需要来这里加一段配置即可。
三、基础映射:同名属性自动转换
最常用也最简单的是同名属性自动映射。例如
AccountService.cs 中新增用户时:
var entity = dto.Adapt<User>();
当 UserDto 和 User
的属性名、类型一致时,Mapster 会自动完成赋值,代码非常清爽。
如果目标对象已经存在,还可以映射到现有实例,避免重复创建对象:
var user = UserData.GetUserObject(dto.UserId);
var entity = dto.Adapt(user); // 把 dto 映射到已有的 user 对象
四、自定义映射:字段名不同或类型转换
实际业务里,源对象和目标对象的字段往往不会完全对应。例如菜单模块中,数据库用
int 表示布尔值(0/1),而前端需要 bool:
// MenuDto -> VueMenu
TypeAdapterConfig<MenuDto, VueMenu>
.NewConfig()
.Map(dest => dest.IsCache, src => src.IsCache > 0)
.Map(dest => dest.IsDelete, src => src.IsDelete > 0)
.Map(dest => dest.IsFrame, src => src.IsFrame > 0)
.Map(dest => dest.Visible, src => src.Visible > 0);
Map(...)
方法可以接收一个表达式,把源字段按需转换后再赋值。反过来,从实体转 DTO
时也可以把 bool 转成 int:
// VueMenu -> MenuDto
TypeAdapterConfig<VueMenu, MenuDto>
.NewConfig()
.Map(dest => dest.IsCache, src => src.IsCache ? 1 : 0)
.Map(dest => dest.IsDelete, src => src.IsDelete ? 1 : 0)
.Map(dest => dest.IsFrame, src => src.IsFrame ? 1 : 0)
.Map(dest => dest.Visible, src => src.Visible ? 1 : 0);
五、映射后处理:AfterMapping
有些字段无法通过简单表达式完成,需要在映射结束后做额外处理。Mapster
提供了 AfterMapping:
TypeAdapterConfig<VueMenu, MenuTree>
.NewConfig()
.AfterMapping((src, dest) =>
{
dest.children = new List<MenuTree>();
dest.meta = new MenuMeta
{
title = src.Text,
icon = src.Icon,
noCache = !src.IsCache
};
dest.hidden = !src.Visible;
});
在 MenuService.cs
中,构建菜单树时大量使用了这种映射方式:
var menuDict = webMenus.ToDictionary(
s => s.Id.ToString(),
s => s.Adapt<MenuTree>());
每个 VueMenu 对象会按照上面的规则映射成前端需要的
MenuTree,包括初始化子节点集合、封装 meta
对象等。
六、自定义整个对象的构建:MapWith
当源集合和目标结构差异较大时,可以用 MapWith
指定一个自定义的构建方法。例如:
TypeAdapterConfig<List<NeTypeView>, List<NeTreeDto>>
.NewConfig()
.MapWith(dtos => BuildTree(dtos));
BuildTree
是一个独立方法,把扁平的设备类型列表按网络类型、厂商、设备类型三层结构组织成树:
public static List<NeTreeDto> BuildTree(IEnumerable<NeTypeView> dtos)
{
var tree = new List<NeTreeDto>();
var networkTypes = dtos.Select(s => new
{
NetworkId = s.NetworkTypeId,
NetworkName = s.NetworkTypeName,
}).Distinct();
foreach (var gnt in networkTypes)
{
var gntNode = new NeTreeDto { ... };
var manufacturers = dtos.Where(s => s.NetworkTypeId == gnt.NetworkId).Distinct();
foreach (var em in manufacturers)
{
var emNode = new NeTreeDto { ... };
gntNode.Children.Add(emNode);
}
tree.Add(gntNode);
}
return tree;
}
调用方只需要一行代码:
var trees = viewDtos.Adapt<List<NeTreeDto>>();
复杂的树形构建逻辑被封装在配置里,业务代码保持简洁。
七、Mapster 与 AutoMapper 的对比
| 特性 | Mapster | AutoMapper |
|---|---|---|
| 映射方式 | 默认编译时生成 | 运行时反射 |
| 性能 | 更高 | 相对较低 |
| 包体积 | 更轻量 | 较大 |
| 配置风格 | 链式 Fluent API | Profile + CreateMap |
| 学习曲线 | 平缓 | 较陡峭 |
| 反向映射 | 支持 TwoWays() |
支持 ReverseMap() |
如果你的项目只是做常见的 DTO/Entity 转换,Mapster 通常足够用,而且性能优势明显。
八、使用建议与注意事项
- 配置集中管理:像 SwitchData 这样把所有映射规则放在
MapsterConfig中,便于查找和维护。 - 避免在热路径里重复配置:
TypeAdapterConfig的配置应该在启动时完成,不要每次请求都注册。 - 注意嵌套映射:Mapster 会自动处理嵌套对象,但如果嵌套类型也需要自定义规则,记得先注册外层再注册内层。
- 善用
AfterMapping:初始化集合、设置默认值、计算派生字段都可以放在这里。 - 调试时生成映射代码:Mapster 支持
TypeAdapterConfig.GlobalSettings.Compiler = CompilerType.Expression;等选项,必要时可以查看生成的表达式树。
九、总结
Mapster 是一个值得在 .NET 项目中尝试的对象映射库。它既能减少样板代码,又能通过编译时映射保证性能。在 SwitchData 项目中,从菜单树构建到用户信息转换,Mapster 的配置式用法让代码结构清晰了很多。
如果你的项目还在手写
new Dto { A = entity.A, B = entity.B },不妨试试
Mapster,把重复劳动交给库,把精力放在业务逻辑上。