.NET8 映射神器 AutoMapper 实战:告别样板代码,对象转换快到飞起

9 阅读4分钟

引言

AutoMapper 是一个用于 .NET 的对象映射库,旨在通过约定和配置将一个对象的属性值自动映射到另一个对象上,从而减少手动编写样板代码的工作量。它常用于实体(Entity)与数据传输对象(DTO)、视图模型(ViewModel)之间的转换。

核心概念与优势

  • 零侵入性:映射配置与业务实体解耦,无需修改原有类结构。
  • 基于约定:默认按同名属性自动映射,并支持深度嵌套对象转换。
  • 可扩展性:允许自定义转换逻辑、条件映射以及使用钩子方法(如 AfterMap/BeforeMap)。
  • 性能优化:通过表达式树预编译映射函数,运行时调用开销较低。

主要组件

组件作用示例
Mapper映射操作入口mapper.Map(src)
Profile映射配置容器继承 Profile 类
Configuration全局配置对象MapperConfiguration
ValueResolver自定义值解析器实现 IValueResolver

安装NuGet包

  • ‌版本要求:使用 AutoMapper 13.0.0+ 版本(旧版需依赖 AutoMapper.Extensions.Microsoft.DependencyInjection)。
Install-Package AutoMapper 
// 这个包已经过时了旧版才需要引用
Install-Package AutoMapper.Extensions.Microsoft.DependencyInjection 

添加了AutoMapper.Extensions.Microsoft.DependencyInjection 程序集,这个程序集主要是为了我们可以通过依赖注入的方式在项目中去使用 AutoMapper。

在 .NET Framework xx 的时代,我们使用 AutoMapper 时,更多的是通过 Mapper 的几个静态方法来实现实体间的映射,不过在 .NET Core 程序中,我们首选还是采用依赖注入的方式去完成实体间的映射。

创建映射规则(Profile类)

推荐为特定领域(如用户、订单)创建继承自 Profile 的类。在构造函数中定义映射规则

public class UserProfile : Profile
{
    public UserProfile()
    {
        // 基础的双向映射
        CreateMap<UserDto, User>();

        // 自定义映射示例:将用户全名组合成一个字段
        CreateMap<User, UserDto>()
            .ForMember(dest => dest.FullName,
                       opt => opt.MapFrom(src => $"{src.LastName} {src.FirstName}"));


    }
}
  • CreateMap<TSource, TDestination>():声明一组映射规则。
  • .ForMember(...):对单个目标属性进行自定义配置。

在Program.cs中注册服务

在.NET 8的 Program.cs 文件中,使用 AddAutoMapper 方法注册服务。它会自动扫描程序集并添加所有 Profile 类。

var builder = WebApplication.CreateBuilder(args);
// 添加其他服务...
builder.Services.AddAutoMapper(typeof(Program).Assembly); // 推荐方式,传入任意程序集中的类型即可
var app = builder.Build();
  • AddAutoMapper(...) 会扫描指定程序集中的 Profile 派生类并注册。
  • 注册后即可通过构造函数注入 IMapper

注入IMapper并使用

在控制器、服务等需要的地方,通过构造函数注入 IMapper 接口,然后调用 Map 方法

[ApiController]
[Route("api/[controller]")]
public class UserController
{
    private readonly IMapper _mapper;
    private readonly IUserService _userService;
    public UserController( IMapper mapper, IUserService userService)
    {
        _mapper = mapper;
        _userService = userService;
    }

    [HttpGet]
    public string GetUser(string id)
    {
        var userEntity = _userService.get(id);
        var userDto = _mapper.Map<UserDto>(userEntity); // 映射单个对象
        return userDto.FullName;
    }
}

约定映射

AutoMapper 默认会按照以下规则自动映射:

  • 属性名相同
    源类型和目标类型中同名的属性会自动映射。
  • 类型兼容
    只要类型兼容(如 int→long、string→DateTime 等),就会自动转换。
  • 嵌套映射
    支持嵌套对象的自动映射。

基础映射场景

简单对象映射

var config = new MapperConfiguration(cfg => 
    cfg.CreateMap<User, UserDto>());

var mapper = config.CreateMapper();
UserDto dto = mapper.Map<UserDto>(userEntity);

集合映射

List<User> users = GetUsers();
List<UserDto> dtos = mapper.Map<List<UserDto>>(users);

反向映射

CreateMap<User, UserDto>().ReverseMap();

// 支持反向转换
User entity = mapper.Map<User>(userDto);

嵌套对象映射

public class Order {
    public User Customer { get; set; }
}

public class OrderDto {
    public UserDto Customer { get; set; }
}

// 自动递归映射嵌套对象
CreateMap<Order, OrderDto>();
CreateMap<User, UserDto>();

高级映射技术

自定义属性映射

CreateMap<User, UserDto>()
    // 简单转换
    .ForMember(dest => dest.FullName, 
               opt => opt.MapFrom(src => $"{src.FirstName} {src.LastName}"))
               
    // 条件映射
    .ForMember(dest => dest.IsAdult,
               opt => opt.Condition(src => src.Age >= 18))
               
    // 忽略特定属性
    .ForMember(dest => dest.Password, opt => opt.Ignore());

条件映射

CreateMap<Product, ProductDto>()
    .ForAllMembers(opt => 
        opt.Condition((src, dest, srcMember) => srcMember != null));
  • 只有当源属性值非 null 时,才执行映射。

忽略属性

cfg.CreateMap<UserDto, UserViewModel>()
    .ForMember(dest => dest.SomeProperty, opt => opt.Ignore());  // 忽略目标属性

继承与多态映射

// 基类
public class AnimalDto
{
    public string Name { get; set; }
}

// 派生类
public class DogDto : AnimalDto
{
    public string Breed { get; set; }
}

// 目标基类
public class AnimalViewModel
{
    public string AnimalName { get; set; }
    public string Type { get; set; }
}

// 目标派生类
public class DogViewModel : AnimalViewModel
{
    public string DogBreed { get; set; }
}

// 配置映射
cfg.CreateMap<AnimalDto, AnimalViewModel>()
    .ForMember(dest => dest.AnimalName, opt => opt.MapFrom(src => src.Name));

cfg.CreateMap<DogDto, DogViewModel>()
    .IncludeBase<AnimalDto, AnimalViewModel>()
    .ForMember(dest => dest.DogBreed, opt => opt.MapFrom(src => src.Breed))
    .ForMember(dest => dest.Type, opt => opt.MapFrom(src => "Dog"));

// 映射
var dogDto = new DogDto { Name = "Buddy", Breed = "Golden Retriever" };
var dogViewModel = mapper.Map<DogViewModel>(dogDto);

全局转换

var config = new MapperConfiguration(cfg =>
{
    // 全局字符串修剪
    cfg.ForAllPropertyMaps(
        pm => pm.DestinationType == typeof(string),
        (pm, c) => c.AddTransform<string>(s => s?.Trim()));
});

BeforeMap / AfterMap 钩子

CreateMap<User, UserDto>()
    .BeforeMap((src, dest) => src.Name = src.Name.ToUpper())
    .AfterMap((src, dest) => dest.Display = $"{dest.Name} ({dest.Id})");

自定义类型转换器

public class DateTimeToStringConverter : ITypeConverter<DateTime, string>
{
    public string Convert(DateTime source, string dest, ResolutionContext ctx)
        => source.ToString("yyyy-MM-dd");
}

CreateMap<DateTime, string>().ConvertUsing<DateTimeToStringConverter>();

使用 ValueResolver

public class TotalPriceResolver : IValueResolver<Order, OrderDto, decimal>
{
    public decimal Resolve(Order source, OrderDto destination, decimal member, ResolutionContext context)
        => source.Items.Sum(item => item.Price * item.Quantity);
}

// 配置解析器
CreateMap<Order, OrderDto>()
    .ForMember(dest => dest.Total, 
               opt => opt.MapFrom<TotalPriceResolver>());

投影(ProjectTo

  • 在 EF Core、LINQ 查询中可直接生成 SQL 投影:
var dtos = dbContext.Users
             .ProjectTo<UserDto>(_mapper.ConfigurationProvider)
             .ToList();

使用特性标记映射

public class UserDto
{
    [AutoMapFrom(typeof(User))]
    public int Id { get; set; }
    
    [SourceMember("FullName")]
    public string DisplayName { get; set; }
}

// 启用特性配置
cfg.AddMaps(typeof(UserDto).Assembly);

动态类型映射

var source = new { Name = "Alice", Age = 30 };
var dest = _mapper.Map<User>(source); // 动态转强类型

// 配置动态映射
CreateMap(typeof(ExpandoObject), typeof(User));

自定义类型转换器

public class MoneyConverter : ITypeConverter<decimal, string>
{
    public string Convert(decimal source, string destination, ResolutionContext context)
        => source.ToString("C");
}

// 全局注册
CreateMap<decimal, string>().ConvertUsing<MoneyConverter>();

条件映射配置

// 根据环境配置不同映射
var config = new MapperConfiguration(cfg => 
{
    if (env.IsDevelopment())
    {
        cfg.CreateMap<User, UserDevDto>();
    }
    else
    {
        cfg.CreateMap<User, UserProdDto>();
    }
});

验证映射配置

// 验证所有映射配置
config.AssertConfigurationIsValid();

// 验证特定映射
config.AssertConfigurationIsValid<UserDto, UserViewModel>();

最佳实践与性能优化

  • 配置集中管理:所有映射配置放在 Profiles 文件夹

  • 严格验证配置:启动时调用 AssertConfigurationIsValid()

  • 避免过度配置:只为真正需要映射的类型创建配置

  • 使用依赖注入:通过 DI 获取 IMapper 实例

  • 分离关注点:不在映射中编写业务逻辑

  • 性能敏感场景:对高频调用路径手动优化

配置验证

var config = new MapperConfiguration(cfg => {
    cfg.CreateMap<User, UserDto>();
    cfg.ValidateMappings(); // 确保所有属性都被映射
});

config.AssertConfigurationIsValid(); // 启动时验证配置

单例模式使用

// 正确:全局使用同一个 Mapper 实例
private static readonly IMapper _mapper;

static MyService()
{
    var config = new MapperConfiguration(cfg => cfg.AddProfile<AppProfile>());
    _mapper = config.CreateMapper();
}

// 错误:每次创建新配置(性能差)
var mapper = new Mapper(new MapperConfiguration(...));

配置编译

var config = new MapperConfiguration(cfg => cfg.CreateMap<Source, Destination>());
config.CompileMappings(); // 显式编译映射

使用静态实例

public static class MapperInstance
{
    public static IMapper Mapper { get; }
    
    static MapperInstance()
    {
        var config = new MapperConfiguration(cfg => {
            // 配置映射
        });
        Mapper = config.CreateMapper();
    }
}

API响应映射

// 统一API响应格式
public class ApiResponse<T>
{
    public T Data { get; set; }
    public bool Success { get; set; }
    public string Message { get; set; }
}

// 在控制器中使用
public IActionResult GetOrder(int id)
{
    var order = _repository.GetOrder(id);
    var dto = _mapper.Map<OrderDto>(order);
    return Ok(new ApiResponse<OrderDto> { Data = dto });
}

忽略未映射属性

// 全局配置
var config = new MapperConfiguration(cfg => 
{
    cfg.CreateMissingTypeMaps = false; // 禁用自动创建映射
    cfg.ShouldMapProperty = p => p.GetMethod.IsPublic; // 只映射公共属性
});

循环引用处理

CreateMap<Parent, ParentDto>()
    .ForMember(dest => dest.Children, opt => opt.Ignore()); // 忽略循环引用属性

调试映射

启用日志记录映射错误

cfg.CreateMap<User, UserDto>().ValidateInlineMaps = true;

避免循环引用

使用 MaxDepth 或 PreserveReferences 处理嵌套对象

CreateMap<User, UserDto>().MaxDepth(2);

引用:

官方文档