在C#企业级开发中,分层架构常常要求把实体类、领域模型与DTO、ViewModel之间进行转换。AutoMapper是一个流行的对象到对象映射库,能帮助开发者减少手工编写赋值代码的工作量,同时降低出错概率。它基于约定优于配置的理念,也支持高度自定义的映射规则。

AutoMapper基础概念
AutoMapper的核心在于“映射配置”和“映射执行”两个动作。映射配置描述源类型与目标类型之间的属性对应关系,映射执行则是根据配置生成目标对象。最常用的两个类型是MapperConfiguration和IMapper。前者在程序启动时一次性构建,后者被注入到业务代码中完成具体转换。
在没有AutoMapper的情况下,我们通常会写大量类似userDto.Name = user.Name;这样的语句。当属性达到几十个,或者存在嵌套对象时,维护成本急剧上升。AutoMapper通过反射与表达式树,在首次映射时编译出高效的赋值委托,后续调用几乎无额外性能损耗。
安装与基础映射
通过NuGet安装AutoMapper包,目前主流版本为AutoMapper 12.x。在控制台项目中可以使用如下命令:
// 使用NuGet包管理器控制台执行 // Install-Package AutoMapper
完成安装后,最基础的用法是创建配置并映射。下面示例展示如何将User实体映射为UserDto:
using AutoMapper;
using System;
public class User
{
public int Id { get; set; }
public string Name { get; set; }
public string Email { get; set; }
}
public class UserDto
{
public int Id { get; set; }
public string Name { get; set; }
public string Email { get; set; }
}
class Program
{
static void Main()
{
// 配置映射关系
var config = new MapperConfiguration(cfg =>
{
cfg.CreateMap<User, UserDto>();
});
// 创建映射器
IMapper mapper = config.CreateMapper();
// 源对象
var user = new User
{
Id = 1,
Name = "张三",
Email = "test@ipipp.com"
};
// 执行映射
UserDto dto = mapper.Map<UserDto>(user);
Console.WriteLine($"Id={dto.Id}, Name={dto.Name}, Email={dto.Email}");
}
}
上述代码中,CreateMap<User, UserDto>()告诉AutoMapper:遇到从User到UserDto的转换请求时,按照同名属性自动赋值。因为两个类的属性名完全一致,所以无需额外配置即可工作。
这种方式的优点是直观、代码量少;缺点是配置散落在代码中,不适合大型项目。实际开发中更推荐把配置集中到Profile里,并结合依赖注入使用。
使用Profile组织映射规则
Profile是AutoMapper提供的配置封装类,允许把同一模块相关的映射写在一起。继承Profile并重写构造函数,在其中调用CreateMap即可。
using AutoMapper;
using System.Collections.Generic;
public class Order
{
public int OrderId { get; set; }
public decimal TotalAmount { get; set; }
public List<OrderItem> Items { get; set; }
}
public class OrderItem
{
public string ProductName { get; set; }
public int Count { get; set; }
}
public class OrderDto
{
public int OrderId { get; set; }
public decimal TotalAmount { get; set; }
public List<OrderItemDto> Items { get; set; }
}
public class OrderItemDto
{
public string ProductName { get; set; }
public int Count { get; set; }
}
public class MappingProfile : Profile
{
public MappingProfile()
{
CreateMap<Order, OrderDto>();
CreateMap<OrderItem, OrderItemDto>();
}
}
在上面的例子中,Order包含Items集合,而OrderDto也包含Items集合。AutoMapper会自动识别集合元素的类型,并复用已配置的OrderItem到OrderItemDto映射,实现嵌套对象转换。
使用Profile后,只需在初始化MapperConfiguration时传入Profile实例,就能加载全部规则。这比在多处写lambda表达式更易测试与维护。
处理属性名称不一致
实际项目中,源类与目标类的字段名往往不同。比如数据库实体使用UserName,而前端DTO使用Account。此时需要用ForMember显式指定。
using AutoMapper;
using System;
public class Member
{
public string UserName { get; set; }
public DateTime Birthday { get; set; }
}
public class MemberDto
{
public string Account { get; set; }
public int Age { get; set; }
}
public class MemberProfile : Profile
{
public MemberProfile()
{
CreateMap<Member, MemberDto>()
.ForMember(dest => dest.Account, opt => opt.MapFrom(src => src.UserName))
.ForMember(dest => dest.Age, opt => opt.MapFrom(src =>
DateTime.Now.Year - src.Birthday.Year));
}
}
class Test
{
static void Run()
{
var config = new MapperConfiguration(cfg => cfg.AddProfile<MemberProfile>());
var mapper = config.CreateMapper();
var m = new Member { UserName = "lilei", Birthday = new DateTime(1990, 5, 1) };
var dto = mapper.Map<MemberDto>(m);
Console.WriteLine(dto.Account + " " + dto.Age);
}
}
ForMember的第一个参数是目标属性表达式,第二个参数通过MapFrom定义取值逻辑。除了简单字段映射,还可以调用方法、计算值、甚至忽略某些属性。
如果目标属性在源中不存在且不需要赋值,可以用.ForMember(dest => dest.X, opt => opt.Ignore())跳过,避免映射器报错或赋予默认值。
在ASP.NET Core中集成依赖注入
Web项目通常不会自己new配置,而是把IMapper注册到DI容器。AutoMapper提供了AutoMapper.Extensions.Microsoft.DependencyInjection包,一行代码即可完成。
// Program.cs 或 Startup.cs 中 using AutoMapper; using Microsoft.Extensions.DependencyInjection; var builder = WebApplication.CreateBuilder(args); // 扫描程序集中所有Profile并注册IMapper builder.Services.AddAutoMapper(typeof(Program).Assembly); var app = builder.Build();
注册之后,在Controller或Service的构造函数中直接注入IMapper即可使用。这样配置在应用启动时构建一次,线程安全且性能稳定。
需要注意的是,若映射配置有错误(比如必填字段无法解析),会在应用启动时抛出异常,而不是在第一次请求时。这其实是一种保护机制,能尽早暴露配置问题。
集合映射与常见坑
AutoMapper天然支持List、Array、IEnumerable等集合类型映射。只要元素类型映射已配置,直接mapper.Map<List<Dto>>(sourceList)就能得到目标列表。
using AutoMapper;
using System.Collections.Generic;
var config = new MapperConfiguration(cfg =>
{
cfg.CreateMap<User, UserDto>();
});
var mapper = config.CreateMapper();
var users = new List<User>
{
new User { Id = 1, Name = "A", Email = "a@ipipp.com" },
new User { Id = 2, Name = "B", Email = "b@ipipp.com" }
};
List<UserDto> dtos = mapper.Map<List<UserDto>>(users);
一个常见误区是认为AutoMapper会修改已有目标对象。实际上默认行为是创建新实例,除非调用mapper.Map(source, existingDest)重载。另外,映射是浅拷贝,如果源对象持有引用类型,目标对象中的对应属性会指向同一引用,修改可能影响原数据。
另一个容易忽视的点是循环引用。若对象图存在互相引用(如订单关联客户,客户又包含订单列表),需配置MaxDepth或改用扁平化DTO,否则映射时可能栈溢出。
总结与最佳实践
AutoMapper的价值在于把“对象转换”这件重复且易错的事标准化。对于简单同名映射,零配置即可生效;对于复杂场景,通过Profile、ForMember、自定义解析器可以灵活应对。
建议在项目中统一使用Profile管理映射,禁止在业务代码里散落new MapperConfiguration。同时把DTO设计得尽量扁平,减少嵌套与循环引用,能让映射更高效、更易排查问题。当字段极少且转换逻辑特殊时,手写映射可能比引入配置更直观,不必强行使用框架。
AutoMapperC#_object_mappingDTO_conversion修改时间:2026-08-04 22:06:19