AutoMapper入门

在 ASP.NET Core 开发中,我们经常需要在不同层之间传递数据:比如从数据库模型(Entity)转换到 DTO,再从 DTO 转换为前端视图模型。这些转换代码大量重复、冗长、容易出错。为了解决这个问题,AutoMapper 诞生了。

本文主要介绍在WebApi(.net 8)环境中使用AutoMapper 14.0.0

官方文档地址:AutoMapper — AutoMapper documentation

一、什么是 AutoMapper?

AutoMapper 是一个 .NET 类库,它能自动在两个类型之间做映射,尤其是当这些类型拥有相似属性时。它减少了手动编写 obj.Prop = dto.Prop 的重复性劳动。

public class Student
{
    public string Name { get; set; }
    public int Age { get; set; }
    public string Address{ get; set; }
    public string Nickname{ get; set; }
}

public class Dto_Student
{
    public string Name { get; set; }
    public int Age { get; set; }
}

可以直接通过 AutoMapper 在这两者之间做转换,而无需显式赋值。

二、基本用法:从零开始配置

1.安装 NuGet 包

dotnet add package AutoMapper --version 14.0.0

13.0开始无需引入AutoMapper.Extensions.Microsoft.DependencyInjection 

2.配置映射关系

创建一个映射配置 Profile 类:

public class StudentProfile : Profile
{
    public StudentProfile()
    {
        CreateMap<Student, Dto_Student>();
    }
}

3.在 Program.cs中注册

using AutoMapper;

namespace AutoMapperProfileConfig
{
    /// <summary>
    /// 标记程序集哑类
    /// </summary>
    public class AutoMapperProfileConfigMarker : Profile
    {
        public AutoMapperProfileConfigMarker()
        {
            
        }
    }
}
builder.Services.AddAutoMapper(typeof(AutoMapperProfileConfigMarker).Assembly);

建议专门建立一个类库,同时为每个表的映射创建一个映射类,然后创建一个专门标记程序集而不做其他事的一个哑类.

这样可以自动扫描该类库里面的所有的Profile类(还有几种其他方法,可自行查看)

4.依赖注入使用映射服务

[ApiController]
[Route("api/[controller]")]
public class StudentController : ControllerBase
{
    private readonly YourDbContext _context;
    private readonly IMapper _mapper;

    public StudentController(YourDbContext context, IMapper mapper)
    {
        _context = context;
        _mapper = mapper;
    }

    // GET: api/Student
    [HttpGet]
    public async Task<ActionResult<IEnumerable<Dto_Student>>> GetStudents()
    {
        // 从数据库获取 Student 实体列表
        var students = await _context.Students.ToListAsync();

        // 使用 AutoMapper 将实体列表映射为 DTO 列表
        var studentDtos = _mapper.Map<List<Dto_Student>>(students);

        // 返回给前端
        return Ok(studentDtos);
    }
}

 从源数据映射到目标数据的过程中默认会忽略null,也不会抛异常.

5.命名约定 

默认情况映射的属性名必须完全一致,但你可以配置实现特殊的映射关系.

AutoMapper 中命名约定的优先级关系如下:

     优先级从高到低:

  1. CreateMap().ForMember() 明确指定字段映射

  2. Profile 中设置的命名约定

  3. 全局设置(MapperConfiguration 中配置)

  4. 默认行为(ExactMatchNamingConvention)

public class Source
{
    public string user_id { get; set; }
}

public class Destination
{
    public string UserId { get; set; }
}

情况 1:使用 .ForMember() 明确映射

CreateMap<Source, Destination>()
    .ForMember(dest => dest.UserId, opt => opt.MapFrom(src => src.user_id));

优先级最高,无论有没有配置命名约定,它都生效

情况 2:Profile 中设置命名约定

public class MyProfile : Profile
{
    public MyProfile()
    {
        SourceMemberNamingConvention = LowerUnderscoreNamingConvention.Instance;
        DestinationMemberNamingConvention = PascalCaseNamingConvention.Instance;

        CreateMap<Source, Destination>();
    }
}

会生效,但仅限于此 Profile 中的 CreateMap 映射。 

情况 3:全局配置命名约定

services.AddAutoMapper(cfg =>
{
    cfg.SourceMemberNamingConvention = LowerUnderscoreNamingConvention.Instance;
    cfg.DestinationMemberNamingConvention = PascalCaseNamingConvention.Instance;
}, typeof(SomeMarker));

会自动应用到所有未手动指定命名规则的 Profile 中的映射

情况 4:都不配置时,默认使用 ExactMatch

即只匹配名字完全一致(区分大小写)的属性名

设置方式作用范围优先级备注
.ForMember()某个字段映射🥇 最高覆盖一切
Profile 中命名约定当前 Profile🥈 中高优于全局配置
cfg.SourceMemberNamingConvention所有 Profile🥉 中等默认使用
不设置使用 ExactMatch最低仅匹配完全相同

6.如何测试映射

#if DEBUG
            using (var scope = app.Services.CreateScope())
            {
                var mapper = scope.ServiceProvider.GetRequiredService<IMapper>();
                mapper.ConfigurationProvider.AssertConfigurationIsValid();
            }
#endif

 这可以在开发时提前测试你配置的映射是否正确

7.配置可见性

默认情况:AutoMapper自动映射public的属性和字段(public属性即便是private setters 也没问题),忽略internal和private,但是你可以覆盖默认的筛选器 ShouldMapField 或 ShouldMapProperty来修改

 builder.Services.AddAutoMapper((cfg) =>
 {
     cfg.ShouldMapProperty= prop =>prop.GetMethod!=null&&(prop.GetMethod.IsPublic || prop.GetMethod.IsAssembly||prop.GetMethod.IsPrivate);

     cfg.ShouldMapField =field=> field.IsPublic || field.IsAssembly || field.IsPrivate;

 }, typeof(AutoMapperProfileConfigMarker).Assembly);
补充解释:
  • p.GetMethod != null:排除掉没有 getter 的属性。

  • IsPublic:表示 public getter。

  • IsAssembly:表示 internal getter(注意:internal 在反射中叫 IsAssembly)。

  • ShouldMapField:是否启用字段映射(多数情况下不需要,但你可以按需开启)。

AutoMapper 与属性访问权限的总结
(1) 属性的访问权限本质是 getter 和 setter 的访问修饰符
  • 在 C# 中,属性本身没有独立的访问修饰符,它的访问权限由 getter 和 setter 的访问修饰符决定。

  • 例如:

    • public string Name { get; set; } —— getter 和 setter 都是 public。

    • public string Name { get; private set; } —— getter 是 public,setter 是 private。

    • private string Age { get; set; } —— getter 和 setter 都是 private。

    • 不可能出现 private string Age { public get; set; }

    • 也不可能出现 private string Age { internal get; set; }

(2) 反射中通过 prop.GetMethod(getter 方法)来判断属性的“可见性”
  • prop.GetMethod 表示属性的 getter 方法。

  • 通过 prop.GetMethod.IsPublicIsPrivateIsAssembly(internal)等判断访问级别。

  • 这就相当于判断属性的访问权限,AutoMapper 使用它决定是否映射某个属性。

(3) AutoMapper 默认只映射公开(public)属性
  • 默认情况下,AutoMapper 只映射 getter 是 public 的属性。

  • 它支持映射 setter 是 private 的属性(setter private 不影响映射)。

  • 不映射 getter 是 internal 或 private 的属性。

(4) 如何让 AutoMapper 识别 internal 或 private 属性
  • 可以通过覆盖配置中的筛选器:

    cfg.ShouldMapProperty = prop =>
        prop.GetMethod != null &&
        (prop.GetMethod.IsPublic || prop.GetMethod.IsAssembly || prop.GetMethod.IsPrivate);
    
  • 这样,AutoMapper 也会映射 internal 和 private getter 的属性。

(5) 映射私有字段也可以
  • 除了属性,还可以配置 ShouldMapField 来映射字段(包括私有字段)。

  • 这对某些隐藏数据很有用,但要谨慎使用,避免破坏封装。

(6) 总结理解
  • 属性的访问权限本质依赖 getter/setter。

  • 反射中通过 prop.GetMethod 判断属性访问权限是合理且准确的。

  • AutoMapper 用它来决定是否映射某个属性,既高效又可靠。

8.投影(Projection)

Projection 就是把一个对象结构转换成另一个更适合使用场景的结构。

举个例子:
你从数据库读到了一个完整的实体类 CalendarEvent,但你在前端表单上不需要完全一样的结构——你希望时间是拆分的、字段名称不同,那么就需要用 AutoMapper 的 Projection 功能来进行“转换 + 结构重组”。


示例拆解说明
原始模型(source)
public class CalendarEvent
{
	public DateTime Date { get; set; }
	public string Title { get; set; }
}

数据库中拿到的实体就是这个类。比如 Date = 2008-12-15 20:30:00


目标模型(destination)
public class CalendarEventForm
{
	public DateTime EventDate { get; set; }
	public int EventHour { get; set; }
	public int EventMinute { get; set; }
	public string Title { get; set; }
}

这个类是用来给前端展示或绑定表单的。你希望:

  • 日期是单独的日期(不带时间);

  • 小时和分钟是两个单独字段;

  • 字段名称也不一样。


AutoMapper 如何配置?
var configuration = new MapperConfiguration(cfg =>
  cfg.CreateMap<CalendarEvent, CalendarEventForm>()
	.ForMember(dest => dest.EventDate, opt => opt.MapFrom(src => src.Date.Date))   // 只取日期部分
	.ForMember(dest => dest.EventHour, opt => opt.MapFrom(src => src.Date.Hour))   // 取小时
	.ForMember(dest => dest.EventMinute, opt => opt.MapFrom(src => src.Date.Minute)) // 取分钟
);

ForMember 是对每个不规则映射字段进行配置
MapFrom 接收一个 lambda 表达式,定义你想怎么从源对象提取值


 MapFrom 背后的原理
opt => opt.MapFrom(src => src.Date.Hour)

这相当于告诉 AutoMapper:

“你在做 CalendarEventCalendarEventForm 映射的时候,EventHour 这个字段别猜了,我来明确告诉你,它等于 src.Date.Hour。”

MapFrom 接受一个委托(lambda 表达式)作为参数,它会在运行时动态调用你提供的这个逻辑。


最终结果
CalendarEventForm form = mapper.Map<CalendarEvent, CalendarEventForm>(calendarEvent);

form.EventDate == new DateTime(2008, 12, 15); // ✅
form.EventHour == 20;                         // ✅
form.EventMinute == 30;                       // ✅
form.Title == "Company Holiday Party";        // ✅

总结
项目含义
Projection结构上的重新组织,目标模型不一定是源模型的“平铺结构”
ForMember指定目标对象某个成员如何映射
MapFrom提供自定义逻辑来指定如何从源对象取值

9.嵌套映射(Nested Mappings)

如何让 AutoMapper 自动将一个类中的嵌套对象,也映射成目标类中对应的嵌套对象。


(1) 背景:嵌套对象和映射需求

假设我们有如下两个类结构:

public class OuterSource
{
    public int Value { get; set; }
    public InnerSource Inner { get; set; }
}

public class InnerSource
{
    public int OtherValue { get; set; }
}

这叫做嵌套结构OuterSource 有个属性 InnerInnerSource 类型,它本身也是一个复杂类型(不是简单的 int、string 等)。

而目标类结构是:

public class OuterDest
{
    public int Value { get; set; }
    public InnerDest Inner { get; set; }
}

public class InnerDest
{
    public int OtherValue { get; set; }
}
(2) 你可以怎么映射?

AutoMapper 有两种选择:

  1. 扁平化映射(Flattening)
    你可以把 OuterSource.Inner.OtherValue 映射成一个扁平属性:

    public class OuterDestFlat
    {
        public int Value { get; set; }
        public int InnerOtherValue { get; set; }
    }
    

    AutoMapper 能够识别这种扁平路径并自动映射。

  2. 嵌套映射(Nested Mapping)
    也可以保持原来的嵌套结构,OuterDest.Inner 依然是个对象。


(3) 如何配置嵌套映射?

AutoMapper 是通过你对内部类型也配置映射关系来支持嵌套的:

var config = new MapperConfiguration(cfg => {
    cfg.CreateMap<OuterSource, OuterDest>();
    cfg.CreateMap<InnerSource, InnerDest>();
});

这里我们定义了两个映射:

  • 外层 OuterSource → OuterDest

  • 内层 InnerSource → InnerDest

AutoMapper 会自动知道:

  • OuterSource.InnerInnerSource

  • OuterDest.InnerInnerDest

  • 已经配置好了这两个类型之间的映射关系

  • 所以会自动递归调用 Map<InnerSource, InnerDest> 来完成嵌套对象的映射


(4) 使用方式
var source = new OuterSource
{
    Value = 5,
    Inner = new InnerSource { OtherValue = 15 }
};

var mapper = config.CreateMapper();
var dest = mapper.Map<OuterSource, OuterDest>(source);

这里只调用了一次 Map<OuterSource, OuterDest>,并不需要你去手动映射内部 InnerSource,AutoMapper 会自动识别并使用我们配置的类型映射去转换嵌套对象。


(5) 注意

配置顺序无关
你不必担心 cfg.CreateMap<InnerSource, InnerDest>() 写在 OuterSource → OuterDest 前还是后,AutoMapper 会在需要的时候查找并使用映射。

嵌套自动映射
你只要调用顶层的 Map<OuterSource, OuterDest>,内部的嵌套对象如果有映射关系,AutoMapper 会递归处理。

灵活生成目标结构
AutoMapper 不仅支持字段名相同的直接映射,还支持扁平化、嵌套、字段转换、组合映射等高级映射需求。


当然,以下是关于 AutoMapper 构造函数映射(Constructor Mapping)的精炼总结


10. AutoMapper 构造函数映射

  1. 默认情况

    • 如果目标类型没有定义构造函数,AutoMapper 使用自动生成的无参构造函数,通过属性赋值完成映射。

  2. 一旦定义了构造函数

    • 默认无参构造函数就不再存在

    • AutoMapper 会尝试使用你定义的构造函数,通过参数名匹配源属性名来自动绑定。

  3. 参数名不匹配时

    • AutoMapper 无法自动绑定,需要使用 .ForCtorParam() 显式指定映射关系。

    cfg.CreateMap<Source, Dest>()
       .ForCtorParam("paramName", opt => opt.MapFrom(src => src.Property));
    
  4. 可选配置

    • 禁用构造函数映射(改用属性赋值):

      cfg.DisableConstructorMapping();
      
    • 指定哪些构造函数可被使用(如仅 public):

      cfg.ShouldUseConstructor = ctor => ctor.IsPublic;
      
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值