# 对象映射 (04_Mapper)
最后更新: 2024-09-20
# 📚 概述
DarkM 对象映射模块基于 AutoMapper 构建,提供对象间自动映射功能,支持模块自动发现和配置注册。
源代码位置: DarkM/src/Framework/Mapper
# 🏗️ 模块架构
# 完整目录结构
Mapper/
└── Mapper.AutoMapper/
├── IMapperConfig.cs # 映射配置接口
├── MapperConfigurationExpression.cs # 映射配置表达式
└── ServiceCollectionExtensions.cs # DI 扩展
1
2
3
4
5
2
3
4
5
# 🔧 核心接口
# 1. IMapperConfig(映射配置接口)
文件位置: Mapper.AutoMapper/IMapperConfig.cs
namespace DarkM.Lib.Mapper.AutoMapper
{
/// <summary>
/// 对象映射绑定
/// </summary>
public interface IMapperConfig
{
/// <summary>
/// 绑定映射配置
/// </summary>
/// <param name="cfg">Mapper 配置表达式</param>
void Bind(IMapperConfigurationExpression cfg);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
说明: 所有映射配置类都必须实现此接口,用于注册映射关系。
# 2. IMapper(映射器接口)
AutoMapper 提供的核心接口,用于执行对象映射:
public interface IMapper
{
/// <summary>
/// 映射对象
/// </summary>
TDestination Map<TDestination>(object source);
/// <summary>
/// 映射对象(指定源类型和目标类型)
/// </summary>
TDestination Map<TSource, TDestination>(TSource source);
/// <summary>
/// 映射对象(到现有实例)
/// </summary>
TDestination Map<TSource, TDestination>(TSource source, TDestination destination);
/// <summary>
/// 投影到 IQueryable
/// </summary>
IQueryable<TDestination> ProjectTo<TDestination>(IQueryable source);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 🏗️ 依赖注入
# ServiceCollectionExtensions
文件位置: Mapper.AutoMapper/ServiceCollectionExtensions.cs
public static class ServiceCollectionExtensions
{
/// <summary>
/// 添加对象映射
/// </summary>
public static IServiceCollection AddMappers(
this IServiceCollection services,
IModuleCollection modules)
{
var config = new MapperConfiguration(cfg =>
{
foreach (var moduleInfo in modules)
{
// 查找所有 IMapperConfig 实现
var types = moduleInfo.AssemblyDescriptor.Application
.GetTypes()
.Where(t => typeof(IMapperConfig).IsAssignableFrom(t));
foreach (var type in types)
{
// 创建实例并绑定配置
((IMapperConfig)Activator.CreateInstance(type)).Bind(cfg);
}
}
});
// 验证配置
config.AssertConfigurationIsValid();
// 注册 IMapper(单例)
services.AddSingleton(config.CreateMapper());
return services;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
说明:
- 自动遍历所有模块
- 自动发现所有
IMapperConfig实现 - 自动注册
IMapper为单例服务
# 💡 配置说明
# 1. 定义映射配置类
在每个 Service 目录中创建 _MapperConfig.cs 文件:
文件位置: Admin.Application/RoleService/_MapperConfig.cs
using AutoMapper;
using DarkM.Lib.Mapper.AutoMapper;
using DarkM.Module.Admin.Application.RoleService.ViewModels;
using DarkM.Module.Admin.Domain.RoleModule.Models;
namespace DarkM.Module.Admin.Application.RoleService
{
/// <summary>
/// 角色映射配置
/// </summary>
public class _MapperConfig : IMapperConfig
{
public void Bind(IMapperConfigurationExpression cfg)
{
// Entity <-> AddModel
cfg.CreateMap<RoleAddModel, RoleEntity>();
// Entity <-> UpdateModel
cfg.CreateMap<RoleEntity, RoleUpdateModel>();
cfg.CreateMap<RoleUpdateModel, RoleEntity>();
// Entity <-> ViewModel
cfg.CreateMap<RoleEntity, RoleViewModel>()
.ForMember(dest => dest.CreatorName,
opt => opt.MapFrom(src => src.Creator.Name));
// Entity <-> QueryModel
cfg.CreateMap<RoleQueryModel, RoleEntity>()
.ForMember(dest => dest.Name,
opt => opt.Condition(src => src.Name != null));
// Entity <-> BindModel
cfg.CreateMap<RoleMenuButtonBindModel, RoleMenuButtonEntity>();
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
命名约定:
- 文件名为
_MapperConfig.cs(下划线开头,始终位于文件列表顶部) - 每个 Service 目录一个映射配置文件
- 集中管理该 Service 的所有映射关系
# 💡 使用示例
# 1. 注入 IMapper
在需要使用映射的地方,注入 IMapper 即可:
using AutoMapper;
public class RoleService : IRoleService
{
// 注入 IMapper
private readonly IMapper _mapper;
private readonly IRoleRepository _repository;
public RoleService(IMapper mapper, IRoleRepository repository)
{
_mapper = mapper;
_repository = repository;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
# 2. 新增操作
public async Task<IResultModel> Add(RoleAddModel model)
{
if (await _repository.Exists(model.Name))
return ResultModel.HasExists;
// 映射:AddModel -> Entity
var entity = _mapper.Map<RoleEntity>(model);
var result = await _repository.AddAsync(entity);
return ResultModel.Result(result);
}
1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
# 3. 更新操作
public async Task<IResultModel> Update(RoleUpdateModel model)
{
var entity = await _repository.GetAsync(model.Id);
if (entity == null)
return ResultModel.NotFound;
// 映射:UpdateModel -> Entity(到现有实例)
_mapper.Map(model, entity);
var result = await _repository.UpdateAsync(entity);
return ResultModel.Result(result);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
# 4. 查询操作
public async Task<IResultModel<RoleViewModel>> Get(int id)
{
var entity = await _repository.GetAsync(id);
if (entity == null)
return ResultModel<RoleViewModel>.NotFound;
// 映射:Entity -> ViewModel
var viewModel = _mapper.Map<RoleViewModel>(entity);
return ResultModel.Success(viewModel);
}
1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
# 5. 分页查询
public async Task<IResultModel<PageModel<RoleViewModel>>> Query(RoleQueryModel model)
{
// 映射:QueryModel -> Entity(用于构建查询条件)
var queryEntity = _mapper.Map<RoleEntity>(model);
var page = await _repository.QueryAsync(queryEntity, model.Page, model.PageSize);
// 映射:Entity List -> ViewModel List
var viewModels = _mapper.Map<List<RoleViewModel>>(page.Data);
return ResultModel.Success(new PageModel<RoleViewModel>
{
Data = viewModels,
Total = page.Total
});
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 🔧 高级用法
# 1. 条件映射
cfg.CreateMap<UserAddModel, UserEntity>()
.ForMember(dest => dest.CreateTime, opt => opt.Ignore()) // 忽略
.ForMember(dest => dest.CreatorId, opt => opt.Ignore())
.ForMember(dest => dest.Status, opt => opt.MapFrom(src => src.Enabled ? 1 : 0)); // 条件映射
1
2
3
4
2
3
4
# 2. 自定义值解析器
public class FullNameResolver : IValueResolver<UserEntity, UserViewModel, string>
{
public string Resolve(UserEntity source, UserViewModel destination, string destMember, ResolutionContext context)
{
return $"{source.FirstName} {source.LastName}";
}
}
// 配置
cfg.CreateMap<UserEntity, UserViewModel>()
.ForMember(dest => dest.FullName, opt => opt.ResolveUsing<FullNameResolver>());
1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
# 3. 嵌套对象映射
cfg.CreateMap<OrderEntity, OrderViewModel>()
.ForMember(dest => dest.CustomerName, opt => opt.MapFrom(src => src.Customer.Name))
.ForMember(dest => dest.CustomerEmail, opt => opt.MapFrom(src => src.Customer.Email));
1
2
3
2
3
# 4. 集合映射
cfg.CreateMap<OrderEntity, OrderViewModel>()
.ForMember(dest => dest.Items, opt => opt.MapFrom(src => src.OrderItems));
// 使用
var orderViewModel = _mapper.Map<OrderViewModel>(orderEntity);
// orderViewModel.Items 会自动映射
1
2
3
4
5
6
2
3
4
5
6
# 5. 投影到 IQueryable
// 使用 ProjectTo 进行数据库层面的投影
var query = _dbContext.Users
.ProjectTo<UserViewModel>(_mapper.ConfigurationProvider);
// 生成的 SQL 只会查询需要的字段
1
2
3
4
5
2
3
4
5
# 6. 反向映射
cfg.CreateMap<UserEntity, UserViewModel>()
.ReverseMap(); // 自动创建反向映射
// 现在可以双向映射
var viewModel = _mapper.Map<UserViewModel>(entity);
var entity = _mapper.Map<UserEntity>(viewModel);
1
2
3
4
5
6
2
3
4
5
6
# 7. ForPath 映射嵌套属性
cfg.CreateMap<UserAddModel, UserEntity>()
.ForPath(dest => dest.Address.Province, opt => opt.MapFrom(src => src.Province))
.ForPath(dest => dest.Address.City, opt => opt.MapFrom(src => src.City));
1
2
3
2
3
# 📚 最佳实践
# 1. 使用 _MapperConfig 命名
将映射配置文件命名为 _MapperConfig.cs,这样会始终位于文件列表顶部,方便查找。
RoleService/
├── _MapperConfig.cs # 映射配置(顶部)
├── RoleService.cs
├── RoleViewModel.cs
└── ViewModels/
1
2
3
4
5
2
3
4
5
# 2. 按 Service 组织映射
每个 Service 目录中放置对应的 _MapperConfig.cs 文件,保持映射关系与业务逻辑在一起。
Admin.Application/
├── RoleService/
│ └── _MapperConfig.cs
├── UserService/
│ └── _MapperConfig.cs
└── MenuService/
└── _MapperConfig.cs
1
2
3
4
5
6
7
2
3
4
5
6
7
# 3. 避免复杂映射
如果映射关系过于复杂,考虑:
- 使用自定义值解析器
- 手动映射部分属性
- 重新设计 ViewModel
# 4. 使用 Ignore 忽略不需要的属性
cfg.CreateMap<UserAddModel, UserEntity>()
.ForMember(dest => dest.Id, opt => opt.Ignore())
.ForMember(dest => dest.CreateTime, opt => opt.Ignore());
1
2
3
2
3
# 5. 使用 MaxDepth 限制循环引用
cfg.CreateMap<UserEntity, UserViewModel>()
.MaxDepth(1); // 限制深度为 1
1
2
2
# 🔍 常见问题
# Q1: 映射不生效?
检查清单:
- ✅ 是否正确实现了
IMapperConfig接口 - ✅ 是否在
Bind方法中配置了映射关系 - ✅ 是否正确注入了
IMapper - ✅ 是否调用了
AddMappers注册映射
解决方案:
// 确保在 Startup.cs 或 Program.cs 中调用
services.AddMappers(modules);
1
2
2
# Q2: 如何处理循环引用?
解决方案:
cfg.CreateMap<UserEntity, UserViewModel>()
.MaxDepth(1); // 限制映射深度
1
2
2
或者使用 Ignore 忽略循环引用属性:
cfg.CreateMap<UserEntity, UserViewModel>()
.ForMember(dest => dest.Orders, opt => opt.Ignore());
1
2
2
# Q3: 性能问题如何优化?
优化方案:
- 使用 ProjectTo 进行数据库层面投影
var query = _dbContext.Users
.ProjectTo<UserViewModel>(_mapper.ConfigurationProvider);
1
2
2
- 避免重复创建 MapperConfiguration
// ✅ 推荐:单例注册
services.AddSingleton(config.CreateMapper());
// ❌ 不推荐:每次请求都创建
var mapper = config.CreateMapper();
1
2
3
4
5
2
3
4
5
- 使用并行映射
var viewModels = entities
.AsParallel()
.Select(e => _mapper.Map<ViewModel>(e))
.ToList();
1
2
3
4
2
3
4
# Q4: 如何映射枚举?
AutoMapper 自动支持枚举映射,无需额外配置:
// Entity
public class UserEntity
{
public Status Status { get; set; }
}
// ViewModel
public class UserViewModel
{
public Status Status { get; set; }
}
// 自动映射,无需配置
cfg.CreateMap<UserEntity, UserViewModel>();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
# Q5: 如何处理 null 值映射?
解决方案:
cfg.CreateMap<UserEntity, UserViewModel>()
.ForMember(dest => dest.Name, opt => opt.Condition(src => src.Name != null));
1
2
2
或者使用 AllowNullDestinationValues:
var config = new MapperConfiguration(cfg =>
{
cfg.AllowNullDestinationValues = true;
});
1
2
3
4
2
3
4
# 📚 相关文档
# 🔗 参考链接
- 源代码 (opens new window) -
src/Framework/Mapper - AutoMapper 官方文档 (opens new window)
- AutoMapper GitHub (opens new window)
最后更新: 2024-09-20