目录
(2)AutoMapperProfile.cs(映射规则)的配置
(1)AutoMapperProfile.cs(映射规则)的配置
一、安装Nuget包
二、控制台程序Demo
1.文件目录
2.程序示例
(1)示例实体
namespace ConsoleApp1
{
public class Model
{
}
public class Student
{
public int Id { get; set; }
public string Name { get; set; }
public int Age { get; set; }
}
public class Teacher
{
public int Id { get; set; }
public string FullName { get; set; }
public int Age { get; set; }
}
}
(2)AutoMapperProfile.cs(映射规则)的配置
【需求】Student
类的 Name
字段映射到 Teacher
类的 FullName
字段
using AutoMapper;
namespace ConsoleApp1
{
public class AutoMapperProfile : Profile
{
public AutoMapperProfile()
{
CreateMap<Student, Teacher>()
.ForMember(dest => dest.FullName, opt => opt.MapFrom(src => src.Name));//配置不同名的字段映射
//其他字段:字段名称一样的自动映射
}
}
}
dest(destination)是目标类的意思,opt是选择的意思,即选择Name的字段映射给FullName。
(3)使用映射(程序入口)
using AutoMapper;
namespace ConsoleApp1
{
internal class Program
{
static void Main(string[] args)
{
// 配置规则
var configuration = new MapperConfiguration(cfg =>
{
cfg.AddProfile<AutoMapperProfile>();
});
// 创建映射器
IMapper mapper = configuration.CreateMapper();
// 创建学生实体
var student = new Student
{
Id = 1,
Name = "小苏",
Age = 20
};
// 映射成老师
var teacher = mapper.Map<Teacher>(student);
Console.WriteLine($"Teacher: Id={teacher.Id}, FullName={teacher.FullName}, Age={teacher.Age}");
//输出:Teacher: Id=1, FullName=小苏, Age=20
}
}
}
三、ABP框架Demo
(1)AutoMapperProfile.cs(映射规则)的配置
配置位置如上,每个应用层都有一个这个类文件,配置内容同控制台程序,就是写个CreateMap<T,V>();
(2)ABP框架下实体映射的三种方式
方案一二(注入):
[Route("api/books")]
public class BookAppService : ApplicationService, IBookAppService
{
private readonly IBookService _bookService;
private readonly IObjectMapper _objectMapper;//using Volo.Abp.ObjectMapping;
public BookAppService(
IBookService bookService,
IObjectMapper objectMapper
)
{
_bookService = bookService;
_objectMapper = objectMapper;
}
[Route("getbookbybll")]
[HttpGet]
public async Task<List<BookDto>> GetListByBLLAsync()
{
List<BookDto> list = await _bookService.GetBooks();//假设有一个服务可以获取List<BookDto>
List<Book> test1 = ObjectMapper.Map<List<BookDto>,List<Book>>(list);//方案一,底层接口或者WebApi控制器继承ApplicationService,直接使用ObjectMapper(前提:应用层配置好映射关系CreateMap)
List<Book> test2 = _objectMapper.Map<List<BookDto>,List<Book>>(list);//方案二,注入IObjectMapper使用(前提:应用层配置好映射关系CreateMap)
return list;
}
}
方案三:
var config = new MapperConfiguration(cfg => cfg.AddProfile<XXXXXXXXAutoMapperProfile>());//XXXXXXXXAutoMapperProfile 配置CreateMap规则
var list = config.CreateMapper().Map<List<源类>, List<目标类>>(list); //list是源类的列表数据
【原理】ABP 框架在启动时通过自动扫描模块中的 Profile 类(继承自 AutoMapper.Profile)并将其自动注册到 IObjectMapper 中,我们可以通过ObjectMapper使用映射。
四、常见配置
1.映射检查及双向映射
1.CreateMap<T,V>()中,允许一个入参控制是否进行映射检查
2.可通过ReverseMap()方法决定是否支持双向映射。
//映射规则:例如可以传入 CreateMap<源类,目标类>(MemberList.Source)
public enum MemberList
{
Destination,//CreateMap不传参默认也是这种,【目标类有未被映射成员=>报错】
Source,//【源类有未映射出去的成员=>报错】
None//【源类,目标类是否被映射都不报错】
}
例如可以写成:
CreateMap<Book, BookDto>(MemberList.None);//不检查是否映射成功(直接实体转为Dto,能转多少是多少)
CreateMap<Book, BookDto>(MemberList.None).ReverseMap();//不检查是否映射成功+双向映射(实体可转为Dto,Dto可转为实体)
2.遍历修改
例如以下规则,就是Student(Name= "John")=>Teacher (FullName ="新名字John")
.ForMember(dest => dest.FullName, opt => opt.MapFrom(src => "新名字" + src.Name));
.ForMember(dest => dest.FullName, opt => opt.MapFrom(src => NameHelper.GetName(src.Name)));//调用某个方法
3.多字段映射写法
CreateMap<Student, Teacher>()
.ForMember(dest => dest.FullName, opt => opt.MapFrom(src => src.Name))
.ForMember(略)
.ForMember(略);
4.映射字段的忽略(含不可访问属性)
假如public string Name { get; } (没有set方法)会报错
解决方案一(封装方法判断此情况,自动跳过):
CreateMap<Student, Teacher>()
.IgnoreAllPropertiesWithAnInaccessibleSetter()
.IgnoreAllSourcePropertiesWithAnInaccessibleSetter(); // 忽略所有设置器不可访问的属性
解决方案二(不选择字段映射到FullName):
CreateMap<Student, Teacher>()
.ForMember(dest => dest.FullName, opt => opt.Ignore()); // 明确忽略特定属性
五、常见问题
1.通用检查方法
请逐步排查:
①是否定义了CreateMap的映射(检查双向映射等)
②定义的映射类型是否准确(看第二点)
③是否调用时写错了类名、混淆了实体和列表(看第三点)
2.类型映射失败
错误信息:
Error mapping types.
Mapping types:
Object -> List`1
System.Object -> System.Collections.Generic.List`1[[XXXDto, Appliction.Contracts, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]
方案:检查CreateMap的映射类型,确保一致(这个也太tm坑了,编译居然没有类型检查,我选择了string映射到decimal? , 启动居然没报错,查询触发才报错,不过改成一样类型的就可以了)
3.映射语法报错
CreateMap定义只写实体即可,不需要写List的
public class StudentProfiles : Profile
{
public StudentProfiles()
{
CreateMap<Student, StudentDto>(); //不需要配置List
}
}
但是映射时则要分清实体和列表。下面是调用的例子:
var list = config.CreateMapper().Map<List<源类>, List<目标类>>(list); //list是源类的列表数据
var entity= config.CreateMapper().Map<源类,目标类>(en); //en是源类的实体数据
4.为什么需要写DTO?
- 保密性:确保数据表加字段不会返回多余的数据
- 简洁性:按需返回字段给前端
- 兼容性:DTO可用于数据转换和格式化
- 灵活性:业务逻辑和数据传输分开,无论表怎么改,DTO实体都是共通的(便于迁移 换库)