AutoMapper 是一个基于命名约定的对象->对象映射工具。
只要2个对象的属性具有相同名字(或者符合它规定的命名约定),AutoMapper就可以替我们自动在2个对象间进行属性值的映射。如果有不符合约定的属性,或者需要自定义映射行为,就需要我们事先告诉AutoMapper,所以在使用 Map(src,dest)进行映射之前,必须使用 CreateMap() 进行配置。
Mapper.CreateMap<Product, ProductDto>(); // 配置 Product entity = Reop.FindProduct(id); // 从数据库中取得实体 Assert.AreEqual("挖掘机", entity.ProductName); ProductDto productDto = Mapper.Map(entity); // 使用AutoMapper自动映射 Assert.AreEqual("挖掘机", productDto.ProductName);
AutoMapper就是这样一个只有2个常用函数的简单方便的工具。不过在实际使用时还是有一些细节需要注意,下面将把比较重要的罗列出来。PS:项目的ORM框架是NHibernate。
1. 在程序启动时执行所有的AutoMapper配置,并且把映射代码放置到一起
下面是一个典型的AutoMapper全局配置代码,里面的一些细节会在后面逐一解释。
1 public class DtoMapping 2 { 3 private readonly IContractReviewMainAppServices IContractReviewMainAppServices; 4 private readonly IDictionaryAppService IDictionaryAppService; 5 private readonly IProductAppService IProductAppService; 6 public DtoMapping(IContractReviewMainAppServices IContractReviewMainAppServices, 7 IDictionaryAppService IDictionaryAppService, IProductAppService IProductAppService) 8 { 9 this.IContractReviewMainAppServices = IContractReviewMainAppServices; 10 this.IDictionaryAppService = IDictionaryAppService; 11 this.IProductAppService = IProductAppService; 12 } 13 14 public void InitMapping() 15 { 16 #region 合同购买设备信息 17 Mapper.CreateMap<ContractReviewProduct, ContractReviewProductDto>(); 18 Mapper.CreateMap<ContractReviewProductDto, ContractReviewProduct>() // DTO 向 Entity 赋值 19 .ForMember(entity => entity.ContractReviewMain, opt => LoadEntity(opt, 20 dto => dto.ContractReviewMainId, 21 IContractReviewMainAppServices.Get)) 22 .ForMember(entity => entity.DeviceCategory, opt => LoadEntity(opt, 23 dto => dto.DeviceCategoryId, 24 IDictionaryAppService.FindDicItem)) 25 .ForMember(entity => entity.DeviceName, opt => LoadEntity(opt, 26 dto => dto.DeviceNameId, 27 IProductAppService.FindProduct)) 28 .ForMember(entity => entity.ProductModel, opt => LoadEntity(opt, 29 dto => dto.ProductModelId, 30 IProductAppService.FindProduct)) 31 .ForMember(entity => entity.Unit, opt => LoadEntity(opt, 32 dto => dto.UnitId, 33 IDictionaryAppService.FindDicItem)) 34 .ForMember(entity => entity.Creator, opt => opt.Ignore()); // DTO 里面没有的属性直接Ignore 35 #endregion 合同购买设备信息 36 37 #region 字典配置 38 Mapper.CreateMap<DicCategory, DicCategoryDto>(); 39 Mapper.CreateMap<DicCategoryDto, DicCategory>(); 40 Mapper.CreateMap<DicItem, DicItemDto>(); 41 Mapper.CreateMap<DicItemDto, DicItem>() 42 .ForMember(entity => entity.Category, opt => LoadEntity(opt, 43 dto => dto.CategoryId, 44 IDictionaryAppService.FindDicCategory)); 45 #endregion 字典配置 46 47 // 对于所有的 DTO 到 Entity 的映射,都忽略 Id 和 Version 属性 48 IgnoreDtoIdAndVersionPropertyToEntity(); 49 50 // 验证配置 51 Mapper.AssertConfigurationIsValid(); 52 } 53 54 /// <summary> 55 /// 加载实体对象。 56 /// <remarks>Id是null的会被忽略;Id是string.Empty的将被赋值为null;Id是GUID的将从数据库中加载并赋值。</remarks> 57 /// </summary> 58 /// <typeparam name="TSource"></typeparam> 59 /// <typeparam name="TMember"></typeparam> 60 /// <param name="opt"></param> 61 /// <param name="getId"></param> 62 /// <param name="doLoad"></param> 63 private void LoadEntity<TSource, TMember>(IMemberConfigurationExpression<TSource> opt, 64 Func<TSource, string> getId, Func<string, TMember> doLoad) where TMember : class 65 { 66 opt.Condition(src => (getId(src) != null)); 67 opt.MapFrom(src => getId(src) == string.Empty ? null : doLoad(getId(src))); 68 } 69 70 /// <summary> 71 /// 对于所有的 DTO 到 Entity 的映射,都忽略 Id 和 Version 属性 72 /// <remarks>当从DTO向Entity赋值时,要保持从数据库中加载过来的Entity的Id和Version属性不变!</remarks> 73 /// </summary> 74 private void IgnoreDtoIdAndVersionPropertyToEntity() 75 { 76 PropertyInfo idProperty = typeof(Entity).GetProperty("Id"); 77 PropertyInfo versionProperty = typeof(Entity).GetProperty("Version"); 78 foreach (TypeMap map in Mapper.GetAllTypeMaps()) 79 { 80 if (typeof(Dto).IsAssignableFrom(map.SourceType) 81 && typeof(Entity).IsAssignableFrom(map.DestinationType)) 82 { 83 map.FindOrCreatePropertyMapFor(new PropertyAccessor(idProperty)).Ignore(); 84 map.FindOrCreatePropertyMapFor(new PropertyAccessor(versionProperty)).Ignore(); 85 } 86 } 87 } 88 }