一、前言
在實際的項目開發過程中,我們使用各種ORM框架可以使我們快捷的獲取到數據,並且可以將獲取到的數據綁定到對應的List<T>中,然后頁面或者接口直接顯示List<T>中的數據。但是我們最終想要顯示在視圖或者接口中的數據和數據庫實體之間可能存在着差異,一般的做法就是去創建一些對應的“模型”類,然后對獲取到的數據再次進行處理,從而滿足需求。
因此,如果便捷的實現數據庫持久化對象與模型對象之間的實體映射,避免在去代碼中手工實現這一過程,就可以大大降低開發的工作量。AutoMapper就是可以幫助我們實現實體轉換過程的工具。
二、使用AutoMapper實現實體映射
AutoMapper是一個OOM(Object-Object-Mapping)組件,從它的英文名字中可以看出,AutoMapper主要是為了實現實體間的相互轉換,從而避免我們每次采用手工的方式進行轉換。在沒有OOM這類組件之前,如果我們需要實現實體之間的轉換,只能使用手工修改代碼,然后逐個賦值的方式實現映射,而有了OOM組件,可以很方便的幫助我們實現這一需求。看下面的一個例子。
首先創建一個ASP.NET Core WebApi項目:
添加一個Student實體類:
namespace AutoMapperDemo.Model { public class Student { public int ID { get; set; } public string Name { get; set; } public int Age { get; set; } public string Gender { get; set; } } }
添加StudentDTO類,跟Student屬性一致。
然后添加一個類,模擬一些測試數據:
using AutoMapperDemo.Model; using System.Collections.Generic; namespace AutoMapperDemo { public class Data { public static List<Student> ListStudent { get; set; } public static List<Student> GetList() { ListStudent = new List<Student>(); for (int i = 0; i < 3; i++) { Student student = new Student() { ID=i, Name=$"測試_{i}", Age=20, Gender="男" }; ListStudent.Add(student); } return ListStudent; } } }
添加Student控制器,通過Get方法獲取所有的值:
using System.Collections.Generic; using System.Threading.Tasks; using AutoMapperDemo.Model; using Microsoft.AspNetCore.Mvc; namespace AutoMapperDemo.Controllers { [Route("api/[controller]")] [ApiController] public class StudentController : ControllerBase { [HttpGet] public async Task<List<Student>> Get() { List<Student> list = new List<Student>(); list = await Task.Run<List<Student>>(() => { return Data.GetList(); }); return list; } } }
使用Postman進行測試:
這樣返回的數據直接就是數據庫對應的實體類類型。這時需求改變了,我們要返回StudentDTO類型的數據,這時就需要修改代碼:
using System.Collections.Generic; using System.Threading.Tasks; using AutoMapperDemo.DTO; using AutoMapperDemo.Model; using Microsoft.AspNetCore.Mvc; namespace AutoMapperDemo.Controllers { [Route("api/[controller]")] [ApiController] public class StudentController : ControllerBase { [HttpGet] public async Task<List<Student>> Get() { List<Student> list = new List<Student>(); list = await Task.Run<List<Student>>(() => { return Data.GetList(); }); return list; } [HttpGet("GetDTO")] public async Task<List<StudentDTO>> GetDto() { List<StudentDTO> list = new List<StudentDTO>(); List<Student> listStudent = await Task.Run<List<Student>>(() => { return Data.GetList(); }); // 循環給屬性賦值 foreach (var item in listStudent) { StudentDTO dto = new StudentDTO(); dto.ID = item.ID; dto.Name = item.Name; dto.Age = item.Age; dto.Gender = item.Gender; // 加入到集合中 list.Add(dto); } return list; } } }
還是使用Postman進行測試:
可以看到:這時返回的是DTO類型的數據。這種情況就是我們上面說的,需要手動修改代碼,然后循環給對應的屬性進行賦值。這里Student類只有4個屬性,如果屬性非常多,或者很多地方使用到了,如果還是采用這種方式進行賦值,那么就會很麻煩。假如以后其中的一個屬性名稱改變了,那么所有的地方也都需要修改,工作量就會很大。這時就需要使用AutoMapper解決。
首先引入AutoMapper包,直接在NuGet中引入:
這里選擇安裝AutoMapper.Extensions.Microsoft.DependencyInjection這個包。這個包主要是為了讓我們可以通過依賴注入的方式去使用AutoMapper。
新建StudentProfile類,繼承自AutoMapper的Profile類,在無參構造函數中,我們就可以通過 CreateMap 方法去創建兩個實體間的映射關系。
using AutoMapper; using AutoMapperDemo.DTO; using AutoMapperDemo.Model; namespace AutoMapperDemo.AutoMapper { /// <summary> /// 繼承自Profile類 /// </summary> public class StudentProfile: Profile { /// <summary> /// 構造函數中實現映射 /// </summary> public StudentProfile() { // Mapping // 第一次參數是源類型(這里是Model類型),第二個參數是目標類型(這里是DTO類型) CreateMap<Student, StudentDTO>(); } } }
這里的 Profile有什么用呢?services.AddAutoMapper他會自動找到所有繼承了Profile的類然后進行配置。
然后修改Student控制器,通過構造函數使用AutoMapper的注入,並使用AutoMapper實現自動映射:
using System.Collections.Generic; using System.Threading.Tasks; using AutoMapper; using AutoMapperDemo.DTO; using AutoMapperDemo.Model; using Microsoft.AspNetCore.Mvc; namespace AutoMapperDemo.Controllers { [Route("api/[controller]")] [ApiController] public class StudentController : ControllerBase { private readonly IMapper _mapper; /// <summary> /// 通過構造函數實現依賴注入 /// </summary> /// <param name="mapper"></param> public StudentController(IMapper mapper) { _mapper = mapper; } [HttpGet] public async Task<List<Student>> Get() { List<Student> list = new List<Student>(); list = await Task.Run<List<Student>>(() => { return Data.GetList(); }); return list; } [HttpGet("GetDTO")] public async Task<List<StudentDTO>> GetDto() { List<StudentDTO> list = new List<StudentDTO>(); List<Student> listStudent = await Task.Run<List<Student>>(() => { return Data.GetList(); }); //// 循環給屬性賦值 //foreach (var item in listStudent) //{ // StudentDTO dto = new StudentDTO(); // dto.ID = item.ID; // dto.Name = item.Name; // dto.Age = item.Age; // dto.Gender = item.Gender; // // 加入到集合中 // list.Add(dto); //} // 使用AutoMapper進行映射 list = _mapper.Map<List<StudentDTO>>(listStudent); return list; } } }
修改Startup類的ConfigureServices方法,添加AutoMapper:
public void ConfigureServices(IServiceCollection services) { #region 使用AutoMapper // 參數類型是Assembly類型的數組 表示AutoMapper將在這些程序集數組里面遍歷尋找所有繼承了Profile類的配置文件 // 在當前作用域的所有程序集里面掃描AutoMapper的配置文件 services.AddAutoMapper(AppDomain.CurrentDomain.GetAssemblies()); #endregion services.AddControllers(); }
再次使用Postman進行測試:
可以看到,這樣也實現了我們的需求,而且還不需要進行手動映射。
上面的示例中,Student和StudentDTO類里面的屬性名稱都是一樣的,如果屬性名稱不一樣呢?我們把StudentDTO類里面的ID改為StudentID,然后修改映射代碼:
using AutoMapper; using AutoMapperDemo.DTO; using AutoMapperDemo.Model; namespace AutoMapperDemo.AutoMapper { /// <summary> /// 繼承自Profile類 /// </summary> public class StudentProfile: Profile { /// <summary> /// 構造函數中實現映射 /// </summary> public StudentProfile() { // Mapping // 第一次參數是源類型(這里是Model類型),第二個參數是目標類型(這里是DTO類型) // CreateMap<Student, StudentDTO>(); // 使用自定義映射 Student類的ID映射到StudentDTO類的StudentID CreateMap<Student, StudentDTO>() .ForMember(destinationMember: des => des.StudentID, memberOptions: opt => { opt.MapFrom(mapExpression: map => map.ID); }); } } }
再次使用Postman進行測試:
這樣就實現了自定義映射。這里是映射了一個字段,如果是多個字段不同呢? 修改StudentDTO類:
namespace AutoMapperDemo.DTO { public class StudentDTO { public int StudentID { get; set; } public string StudentName { get; set; } public int StudentAge { get; set; } public string StudentGender { get; set; } } }
然后修改映射配置類:
using AutoMapper; using AutoMapperDemo.DTO; using AutoMapperDemo.Model; namespace AutoMapperDemo.AutoMapper { /// <summary> /// 繼承自Profile類 /// </summary> public class StudentProfile: Profile { /// <summary> /// 構造函數中實現映射 /// </summary> public StudentProfile() { // Mapping // 第一次參數是源類型(這里是Model類型),第二個參數是目標類型(這里是DTO類型) // CreateMap<Student, StudentDTO>(); // 使用自定義映射 Student類的ID映射到StudentDTO類的StudentID //CreateMap<Student, StudentDTO>() // .ForMember(destinationMember: des => des.StudentID, memberOptions: opt => { opt.MapFrom(mapExpression: map => map.ID); }); // 對多個屬性進行自定義映射 CreateMap<Student, StudentDTO>() .ForMember(destinationMember: des => des.StudentID, memberOptions: opt => { opt.MapFrom(mapExpression: map => map.ID); }) .ForMember(destinationMember: des => des.StudentName, memberOptions: opt => { opt.MapFrom(mapExpression: map => map.Name); }) .ForMember(destinationMember: des => des.StudentAge, memberOptions: opt => { opt.MapFrom(mapExpression: map => map.Age); }) .ForMember(destinationMember: des => des.StudentGender, memberOptions: opt => { opt.MapFrom(mapExpression: map => map.Gender); }); } } }
在使用Postman進行測試:
這樣就實現了多個屬性的自定義映射。
上面的實例中是從Student映射到StudentDTO,那么可以從StudentDTO映射到Student嗎?答案是肯定的,只需要在映射的最后使用ReverseMap()方法即可:
using AutoMapper; using AutoMapperDemo.DTO; using AutoMapperDemo.Model; namespace AutoMapperDemo.AutoMapper { /// <summary> /// 繼承自Profile類 /// </summary> public class StudentProfile: Profile { /// <summary> /// 構造函數中實現映射 /// </summary> public StudentProfile() { // Mapping // 第一次參數是源類型(這里是Model類型),第二個參數是目標類型(這里是DTO類型) // CreateMap<Student, StudentDTO>(); // 使用自定義映射 Student類的ID映射到StudentDTO類的StudentID //CreateMap<Student, StudentDTO>() // .ForMember(destinationMember: des => des.StudentID, memberOptions: opt => { opt.MapFrom(mapExpression: map => map.ID); }); // 對多個屬性進行自定義映射 CreateMap<Student, StudentDTO>() .ForMember(destinationMember: des => des.StudentID, memberOptions: opt => { opt.MapFrom(mapExpression: map => map.ID); }) .ForMember(destinationMember: des => des.StudentName, memberOptions: opt => { opt.MapFrom(mapExpression: map => map.Name); }) .ForMember(destinationMember: des => des.StudentAge, memberOptions: opt => { opt.MapFrom(mapExpression: map => map.Age); }) .ForMember(destinationMember: des => des.StudentGender, memberOptions: opt => { opt.MapFrom(mapExpression: map => map.Gender); }) // ReverseMap表示雙向映射 .ReverseMap(); } } }
我們修改Data,里面增加一個Add方法,可以將Student添加到集合中:
using AutoMapperDemo.Model; using System.Collections.Generic; namespace AutoMapperDemo { public class Data { public static List<Student> ListStudent { get; set; } static Data() { ListStudent = new List<Student>(); for (int i = 0; i < 3; i++) { Student student = new Student() { ID = i, Name = $"測試_{i}", Age = 20, Gender = "男" }; ListStudent.Add(student); } } public static List<Student> GetList() { return ListStudent; } public static void Add(Student entity) { ListStudent.Add(entity); } } }
修改Student控制器,添加一個Post方法,傳入的參數的StudentDTO類型:
using System.Collections.Generic; using System.Threading.Tasks; using AutoMapper; using AutoMapperDemo.DTO; using AutoMapperDemo.Model; using Microsoft.AspNetCore.Mvc; namespace AutoMapperDemo.Controllers { [Route("api/[controller]")] [ApiController] public class StudentController : ControllerBase { private readonly IMapper _mapper; /// <summary> /// 通過構造函數實現依賴注入 /// </summary> /// <param name="mapper"></param> public StudentController(IMapper mapper) { _mapper = mapper; } [HttpGet] public async Task<List<Student>> Get() { List<Student> list = new List<Student>(); list = await Task.Run<List<Student>>(() => { return Data.GetList(); }); return list; } [HttpGet("GetDTO")] public async Task<List<StudentDTO>> GetDto() { List<StudentDTO> list = new List<StudentDTO>(); List<Student> listStudent = await Task.Run<List<Student>>(() => { return Data.GetList(); }); //// 循環給屬性賦值 //foreach (var item in listStudent) //{ // StudentDTO dto = new StudentDTO(); // dto.ID = item.ID; // dto.Name = item.Name; // dto.Age = item.Age; // dto.Gender = item.Gender; // // 加入到集合中 // list.Add(dto); //} // 使用AutoMapper進行映射 list = _mapper.Map<List<StudentDTO>>(listStudent); return list; } [HttpPost] public async Task<List<Student>> Post([FromBody]StudentDTO entity) { List<Student> list = new List<Student>(); // 將StudentDTO反向映射為Student類型 Student student = _mapper.Map<Student>(entity); // 添加到集合中 Data.Add(student); // 返回增加后的數組,這里返回Student list = await Task.Run<List<Student>>(() => { return Data.GetList(); }); return list; } } }
使用Postman進行測試:
返回結果:
這樣就實現了映射的反轉。
具體其它API功能,參考AutoMapper官網:https://automapper.readthedocs.io/en/latest/index.html