Appearance
AutoMapper 对象映射实践
AutoMapper 是一个对象-对象映射器,核心作用是解决手工编写大量
dto.Name = entity.Name这类样板代码的问题。主要用于将复杂的领域模型(Domain Model)展平为简单的传输数据模型(DTO)。
一、 为什么使用 AutoMapper
在三层架构或 DDD 架构中,数据模型(Entity)和数据传输对象(DTO)通常是分离的:
- 解耦:不直接向外暴露数据库实体结构。
- 安全性:防止外部直接修改敏感字段。
- 性能:按需返回字段,减少序列化体积。
如果不使用工具,手工映射会产生大量枯燥且易错的赋值代码。AutoMapper 通过约定优于配置的原则,基于属性名称自动匹配,极大减少了手工赋值的工作量。
二、 基础使用流程
1. 定义源与目标类
csharp
public class Foo
{
public int ID { get; set; }
public string Name { get; set; }
}
public class FooDto
{
public int ID { get; set; }
public string Name { get; set; }
}2. 配置与注册
在使用 Map 方法之前,必须告诉 AutoMapper 哪些类可以映射。每个 AppDomain 只能进行一次配置,通常放在应用程序启动时(如 Startup.cs 或 Global.asax)。
csharp
var config = new MapperConfiguration(cfg => cfg.CreateMap<Foo, FooDto>());
var mapper = config.CreateMapper();推荐使用 Profile 来组织映射,而不是全写在一起:
csharp
public class EmployeeProfile : Profile
{
public EmployeeProfile()
{
CreateMap<Employee, EmployeeDto>();
}
}
// 在启动时注册
var config = new MapperConfiguration(cfg =>
{
cfg.AddProfile<EmployeeProfile>();
// 或者直接扫描当前程序集
// cfg.AddMaps(System.AppDomain.CurrentDomain.GetAssemblies());
});3. 执行映射
csharp
Foo foo = new Foo { ID = 1, Name = "Tom" };
FooDto dto = mapper.Map<FooDto>(foo);三、 常见高级场景
1. 扁平化映射 (Flattening)
对象-对象映射的常见用法是将复杂的嵌套对象模型展平为更简单的模型。 如果目标属性的名称与源类型的 类名+属性名 对应,AutoMapper 会自动进行拆解和匹配。
csharp
public class Employee
{
public string Name { get; set; }
public Department Department { get; set; }
}
public class Department
{
public int ID { get; set; }
public string Name { get; set; }
}
public class EmployeeDto
{
public string Name { get; set; }
public int DepartmentID { get; set; } // 自动映射到 Employee.Department.ID
public string DepartmentName { get; set; } // 自动映射到 Employee.Department.Name
}2. 自定义映射规则
当源类型与目标类型名称不一致,或需要对数据做简单转换时,可以通过 ForMember 自定义规则。
csharp
var config = new MapperConfiguration(cfg =>
{
cfg.CreateMap<Employee, EmployeeDto>()
// 属性名不一致的映射
.ForMember(dest => dest.EmployeeID, opt => opt.MapFrom(src => src.ID))
// 简单的数据转换
.ForMember(dest => dest.JoinYear, opt => opt.MapFrom(src => src.JoinTime.Year));
});3. 数组和列表映射
集合的映射非常简单,只需配置元素类型的映射关系,无需单独为 List<T> 配置。
csharp
cfg.CreateMap<Source, Destination>();
IMapper mapper = config.CreateMapper();
var sources = new[]
{
new Source { Value = 5 },
new Source { Value = 6 }
};
// 支持多种集合类型
IEnumerable<Destination> ienum = mapper.Map<Source[], IEnumerable<Destination>>(sources);
List<Destination> list = mapper.Map<Source[], List<Destination>>(sources);⚠️ 空集合处理:当源集合为
null时,AutoMapper 默认会将目标映射为空集合(Empty Collection),而不是null。这符合框架设计规范(IEnumerable 永远不应该为 null)。
4. 方法到属性的映射
如果源对象包含无参方法(如 GetFullName()),它可以直接映射到目标对象的同名属性(如 FullName),无需任何额外配置。
csharp
public class Employee
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string GetFullName()
{
return $"{FirstName} {LastName}";
}
}
public class EmployeeDto
{
// 会自动调用 GetFullName() 并赋值
public string FullName { get; set; }
}5. 命名约定冲突处理
如果源和目标采用了不同的命名规范(如蛇形命名法与帕斯卡命名法),可以全局指定命名约定,而不需要一个个手动映射。
csharp
var config = new MapperConfiguration(cfg =>
{
cfg.CreateMap<Foo, FooDto>();
cfg.SourceMemberNamingConvention = new PascalCaseNamingConvention();
cfg.DestinationMemberNamingConvention = new LowerUnderscoreNamingConvention();
});四、 常见踩坑与注意事项
不要滥用 AutoMapper
- 映射逻辑应该是简单的。如果一个
ForMember里面写了大量的业务判断和计算,说明这个逻辑属于业务层,而不是映射层。 - 遇到这种情况,应当在业务代码里显式处理,或者提炼到实体的方法中,保持 AutoMapper 职责单一。
- 映射逻辑应该是简单的。如果一个
私有属性与构造函数映射
- 默认情况下 AutoMapper 仅映射
public成员。 - 如果目标类没有公共的
set方法(如通过构造函数初始化的只读 DTO),AutoMapper 也能自动匹配构造函数进行传参。
- 默认情况下 AutoMapper 仅映射
嵌套映射
- 扁平化是将内部对象的属性提取出来;如果需要保留嵌套结构(如
EmployeeDto内包含DepartmentDto),只需确保内部的类(Department到DepartmentDto)也配置了CreateMap即可,AutoMapper 会自动递归处理。
- 扁平化是将内部对象的属性提取出来;如果需要保留嵌套结构(如
