在我們的業務中經常需要使用到類型之間的映射,特別是在和前端頁面進行交互的時候,我們需要定義各種類型的Dto,並且需要需要這些Dto和數據庫中的實體進行映射,對於有些大對象而言,需要賦值太多的屬性,這樣往往會使整個代碼不夠簡潔和明了,有了AutoMapper之后我們就可以通過很少的代碼來完成這樣一個映射的過程,在了解當前的代碼之前,你最好先讀一下ABP文檔中對於這個部分的介紹,更詳細的介紹你可以參考這里。
一 基礎篇
1 注入IObjectMapper接口
通過接口注入IObjectMapper對象,如果使用ABP框架的話所有繼承自ApplicationService的應用服務都可以獲取定義於AbpServiceBase中已經注入的公用屬性ObjectMapper對象。
2 定義映射關系
在當前繼承自AbpModule的類下面的Initialize()方法中添加映射關系
public override void Initialize() {
IocManager.RegisterAssemblyByConvention(typeof(DcsApplicationModule).GetAssembly());
IocManager.AddSdtSession<Guid>();
Configuration.Modules.AbpAutoMapper().Configurators.Add(config => {
config.CreateMissingTypeMaps = true;
#region 客戶售后檔案,客戶售后檔案與車輛關系
config.CreateMap<CustomerSoldDto, CustomerSold>(MemberList.Source);
config.CreateMap<CustomerVehicleSoldDto, CustomerVehicleSold>(MemberList.Source);
config.CreateMap<CustomerSoldExtendedInput, CustomerSoldExtended>(MemberList.Source)
.ForMember(c => c.C4cmCode, o => o.MapFrom(m => m.CrmCode))
.ForMember(c => c.ReferrerPhone, o => o.MapFrom(m => m.Referrer));
config.CreateMap<CustomerSoldSyncInput, CustomerSold>(MemberList.Source)
.ForMember(c => c.CustomerSoldExtended, o => o.MapFrom(m => m.CustomerSoldExtended))
.ForMember(c => c.JobName, o => o.MapFrom(m => m.JobTitle));
#endregion 客戶售后檔案,客戶售后檔案與車輛關系
});
}
上面的代碼中我們可以使用ForMember方法來定義自己的映射規則。
3 使用ObjectMapper映射實體關系
在有了第一步的工作后,我們就可以在業務代碼中調用_objectMapper.Map<T>(input),或者是_objectMapper.Map(input,oldEntity)這種方式來實現從Dto到數據庫實體的映射關系。
二 進階篇
1 定義Profile來分散映射關系
如果我們把所有的映射關系都寫在Module的Initialize()方法里面,我們發現對於一個大的項目簡直就是一個災難,因為這個映射關系實在是太多,整個類超級大,現在有一個解決方案就是定義自己的XXXProfile,但是需要繼承自基類Profile類。
public class EngineMaintainApplyProfile : Profile {
public EngineMaintainApplyProfile() {
CreateMap<EngineMaintainApply, QueryEngineMaintainApplyOutput>(MemberList.Destination)
.ForMember(d => d.BreakMaintainMark, options => options.MapFrom(s => s.VehicleSold.BreakMaintainMark));
CreateMap<EngineMaintainApplyAth, EngineMaintainApplyAthDto>(MemberList.Destination);
CreateMap<EngineMaintainApply, QueryEngineMaintainApplyWithDetailsOutput>(MemberList.Destination)
.ForMember(d => d.Attachments, options => options.MapFrom(s => s.EngineMaintainApplyAths))
.ForMember(d => d.ProductCode, options => options.MapFrom(s => s.VehicleSold.ProductCode))
.ForMember(d => d.EngineCode, options => options.MapFrom(s => s.VehicleSold.EngineCode))
.ForMember(d => d.TransmissionSn, options => options.MapFrom(s => s.VehicleSold.TransmissionSn))
.ForMember(d => d.Color, options => options.MapFrom(s => s.VehicleSold.Color))
.ForMember(d => d.VehiclePurpose, options => options.MapFrom(s => s.VehicleSold.VehiclePurpose))
.ForMember(d => d.InvoiceDate, options => options.MapFrom(s => s.VehicleSold.InvoiceDate))
.ForMember(d => d.SalePrice, options => options.MapFrom(s => s.VehicleSold.SalePrice))
.ForMember(d => d.ProductionDate, options => options.MapFrom(s => s.VehicleSold.ProductionDate))
.ForMember(d => d.VehicleSaleDate, options => options.MapFrom(s => s.VehicleSold.SaleDate));
CreateMap<AddEngineMaintainApplyInput, EngineMaintainApply>(MemberList.Source)
.ForMember(d => d.VehicleSoldId, options => options.MapFrom(s => s.VehicleId))
.ForMember(d => d.ExtensionMaintainBeginDate, options => options.MapFrom(s => s.SaleDate))
.ForMember(d => d.EngineMaintainApplyAths, options => options.Ignore());
CreateMap<UpdateEngineMaintainApplyInput, EngineMaintainApply>(MemberList.Source)
.ForMember(d => d.RowVersion, options => options.Ignore())
.ForMember(d => d.EngineMaintainApplyAths, options => options.Ignore());
}
}
使用這個方法的時候需要注意需要在Initialize方法里面添加映射,例如下面的方式。
public override void Initialize() {
IocManager.RegisterAssemblyByConvention(typeof(DcsApplicationModule).GetAssembly());
Configuration.Modules.AbpAutoMapper().Configurators.Add(config => {
config.AddMaps(typeof(DcsApplicationModule).GetAssembly());
});
}
2 添加自動映射關系
這個需要特別注意,當我們的Dto和目標實體每個字段都能一一對應的情況下,在AutoMapper<8.1.1這個版本的時候能夠自動識別並映射,但是當升級到這個版本的時候這個特性會去掉,關於這個內容請點擊這里了解詳情,AutoMapper的官方暫時給了一個過渡的方案,那就是設置CreateMissingTypeMaps屬性設置為true,就像下面的例子。
public override void Initialize() {
IocManager.RegisterAssemblyByConvention(typeof(DcsApplicationModule).GetAssembly());
Configuration.Modules.AbpAutoMapper().Configurators.Add(config => {
config.CreateMissingTypeMaps = true;
});
}
但是這只是一個過渡方案,在這個屬性的上面加了一個Obsolete標簽,並說明在Version9.0的時候會去掉,所以后面我們也是需要去顯式去添加映射關系。
/// <summary>
/// Create missing type maps during mapping, if necessary
/// </summary>
[Obsolete("Support for automatically created maps will be removed in version 9.0. You will need to explicitly configure maps, manually or using reflection. Also consider attribute mapping (http://docs.automapper.org/en/latest/Attribute-mapping.html).")]
bool ? CreateMissingTypeMaps { get; set; }
3 部分對象的忽略
有的時候我們將源映射到目標的時候,希望忽略掉一些字段的賦值,比如Id,我們希望框架來自動為我們的實體來添加主鍵Id,那么我們應該怎么處理呢?
public override void Initialize() {
IocManager.RegisterAssemblyByConvention(typeof(DcsApplicationModule).GetAssembly());
Configuration.Modules.AbpAutoMapper().Configurators.Add(config => {
config.CreateMissingTypeMaps = true;
config.CreateMap<VehicleSoldDto, VehicleSold>(MemberList.Source)
.ForMember(d => d.Id, o => o.Ignore());
});
}
3 ProjectTo方法
AutoMapper為我們提供了一個IQueryable類型的擴展,能夠很方便的讓我們進行關系之間的映射,我們來看看具體的源碼,然后再看看在具體的項目中是如何使用的?
/// <summary>
/// Extension method to project from a queryable using the provided mapping engine
/// </summary>
/// <remarks>Projections are only calculated once and cached</remarks>
/// <typeparam name="TDestination">Destination type</typeparam>
/// <param name="source">Queryable source</param>
/// <param name="configuration">Mapper configuration</param>
/// <param name="membersToExpand">Explicit members to expand</param>
/// <returns>Expression to project into</returns>
public static IQueryable<TDestination> ProjectTo<TDestination>(
this IQueryable source,
IConfigurationProvider configuration,
params Expression<Func<TDestination, object>>[] membersToExpand
)
=> source.ProjectTo(configuration, null, membersToExpand);
這個快捷方法能夠讓我們不需要通過ObjectMapper.Map方法來快速進行對象之間的映射,這里需要注意的是這時候這兩個對象之間也需要在Profile或者是Initialize方法中通過CreateMap來指定映射關系,這里我們來看看在具體的業務中該如何使用。
/// <summary>
/// 查詢維修預約單
/// </summary>
/// <param name="input">查詢輸入</param>
/// <param name="pageRequest">分頁輸入</param>
/// <returns>帶分頁的維修預約單信息</returns>
public async Task<Page<QueryRepairAppointmentsOutput>> QueryRepairAppointmentsAsync(QueryRepairAppointmentsInput input, PageRequest pageRequest) {
var queryResults = _repairAppointmentRepository.GetAll()
.Include(a => a.ServiceAdvisor)
.Where(a => a.DealerId == SdtSession.TenantId) // 經銷商Id=登錄企業Id
.WhereIf(!string.IsNullOrWhiteSpace(input.Code), a => a.Code.Contains(input.Code))
.WhereIf(!string.IsNullOrWhiteSpace(input.Vin), a => a.Vin.Contains(input.Vin))
.WhereIf(!string.IsNullOrWhiteSpace(input.LicensePlate), a => a.LicensePlate.Contains(input.LicensePlate))
.WhereIf(!string.IsNullOrWhiteSpace(input.CustomerName), a => a.CustomerName.Contains(input.CustomerName))
.WhereIf(!string.IsNullOrWhiteSpace(input.ServiceAdvisorName), a => a.ServiceAdvisor.Name.Contains(input.ServiceAdvisorName))
.WhereIf(input.OrderChannel?.Length > 0, a => input.OrderChannel.Contains(a.OrderChannel))
.WhereIf(input.Status?.Length > 0, a => input.Status.Contains(a.Status))
.WhereIf(input.BeginPlanArriveDate.HasValue, a => input.BeginPlanArriveDate.Value <= a.PlanArriveDate)
.WhereIf(input.EndPlanArriveDate.HasValue, a => a.PlanArriveDate <= input.EndPlanArriveDate)
.WhereIf(input.BeginArriveTime.HasValue, a => input.BeginArriveTime <= a.ArriveTime)
.WhereIf(input.EndArriveTime.HasValue, a => a.ArriveTime <= input.EndArriveTime)
.WhereIf(input.BeginCreateTime.HasValue, a => input.BeginCreateTime <= a.CreateTime)
.WhereIf(input.EndCreateTime.HasValue, a => a.CreateTime <= input.EndCreateTime);
var totalCount = await queryResults.CountAsync();
var pagedResults = await queryResults.ProjectTo<QueryRepairAppointmentsOutput>(_autoMapper.ConfigurationProvider)
.PageAndOrderBy(pageRequest).ToListAsync();
return new Page<QueryRepairAppointmentsOutput>(pageRequest, totalCount, pagedResults);
}
這里通過一個ProjectTo<T>的方法就能夠實現從源到目標的映射,這里需要注意的是這個方法中的_autoMapper是一個IMapper注入的對象,這個參數的注入到底有什么用呢?具體也不是十分清楚,但是在單元測試中需要添加此參數,這個可以去嘗試。
4 AutoMapTo、AutoMapFrom
簡單對象之間的映射關系,可以直接通過給類型打標簽的方式來進行,這樣就不用自己去通過CreateMap來添加映射了。
[AutoMapTo(typeof(User))]
public class CreateUserInput
{
public string Name { get; set; }
public string Surname { get; set; }
public string EmailAddress { get; set; }
public string Password { get; set; }
}
最后,點擊這里返回整個ABP系列的主目錄。
