AutoMapper的匹配
1,智能匹配
AutoMapper能够自动识别和匹配大部分对象属性:
-
- 如果源类和目标类的属性名称相同,直接匹配,不区分大小写
-
- 目标类型的CustomerName可以匹配源类型的Customer.Name
-
- 目标类型的Total可以匹配源类型的GetTotal()方法
2,自定义匹配
Mapper.CreateMap<CalendarEvent, CalendarEventForm>() //属性匹配,匹配源类中WorkEvent.Date到EventDate
.ForMember(dest => dest.EventDate, opt => opt.MapFrom(src => src.WorkEvent.Date))
.ForMember(dest => dest.SomeValue, opt => opt.Ignore()) //忽略目标类中的属性
.ForMember(dest => dest.TotalAmount, opt => opt.MapFrom(src => src.TotalAmount ?? 0)) //复杂的匹配
.ForMember(dest => dest.OrderDate, opt => opt.UserValue<DateTime>(DateTime.Now)); //固定值匹配
直接匹配
源类的属性名称和目标类的属性名称相同(不区分大小写),直接匹配,Mapper.CreateMap<source,dest>();无需做其他处理,此处不再细述
Flattening
将一个复杂的对象模型拉伸为,或者扁平化为一个简单的对象模型,如下面这个复杂的对象模型:
public class Order { private readonly IList<OrderLineItem> _orderLineItems = new List<OrderLineItem>(); public Customer Customer { get; set; } public OrderLineItem[] GetOrderLineItems() { return _orderLineItems.ToArray(); } public void AddOrderLineItem(Product product, int quantity) { _orderLineItems.Add(new OrderLineItem(product, quantity)); } public decimal GetTotal() { return _orderLineItems.Sum(li => li.GetTotal()); } } public class Product { public decimal Price { get; set; } public string Name { get; set; } } public class OrderLineItem { public OrderLineItem(Product product, int quantity) { Product = product; Quantity = quantity; } public Product Product { get; private set; } public int Quantity { get; private set; } public decimal GetTotal() { return Quantity * Product.Price; } } public class Customer { public string Name { get; set; } }
我们要把这一复杂的对象简化为OrderDTO,只包含某一场景所需要的数据:
public class OrderDto { public string CustomerName { get; set; } public decimal Total { get; set; } }
运用AutoMapper转换:
public void Example() { // Complex model var customer = new Customer { Name = "George Costanza" }; var order = new Order { Customer = customer }; var bosco = new Product { Name = "Bosco", Price = 4.99m }; order.AddOrderLineItem(bosco, 15); // Configure AutoMapper var config = new MapperConfiguration(cfg => cfg.CreateMap<Order, OrderDto>()); // Perform mapping var mapper = config.CreateMapper(); OrderDto dto = mapper.Map<Order, OrderDto>(order); dto.CustomerName.ShouldEqual("George Costanza"); dto.Total.ShouldEqual(74.85m); }
可以看到只要设置下Order和OrderDto之间的类型映射就可以了,我们看OrderDto中的CustomerName和Total属性在领域模型Order中并没有与之相对性,AutoMapper在做解析的时候会按照PascalCase(帕斯卡命名法),CustomerName其实是由Customer+Name 得来的,是AutoMapper的一种映射规则;而Total是因为在Order中有GetTotal()方法,AutoMapper会解析“Get”之后的单词,所以会与Total对应。在编写代码过程中可以运用这种规则来定义名称实现自动转换。
Projection
Projection可以理解为与Flattening相反,Projection是将源对象映射到一个不完全与源对象匹配的目标对象,需要制定自定义成员,如下面的源对象:
public class CalendarEvent { public DateTime EventDate { get; set; } public string Title { get; set; } }
目标对象:
public class CalendarEventForm { public DateTime EventDate { get; set; } public int EventHour { get; set; } public int EventMinute { get; set; } public string Title { get; set; } }
AutoMapper配置转换代码:
public void Example() { // Model var calendarEvent = new CalendarEvent { EventDate = new DateTime(2008, 12, 15, 20, 30, 0), Title = "Company Holiday Party" }; var config = new MapperConfiguration(cfg => { // Configure AutoMapper cfg.CreateMap<CalendarEvent, CalendarEventForm>() .ForMember(dest => dest.EventDate, opt => opt.MapFrom(src => src.EventDate.Date)) .ForMember(dest => dest.EventHour, opt => opt.MapFrom(src => src.EventDate.Hour)) .ForMember(dest => dest.EventMinute, opt => opt.MapFrom(src => src.EventDate.Minute)); }); // Perform mapping var mapper = config.CreateMapper(); CalendarEventForm form = mapper.Map<CalendarEvent, CalendarEventForm>(calendarEvent); form.EventDate.ShouldEqual(new DateTime(2008, 12, 15)); form.EventHour.ShouldEqual(20); form.EventMinute.ShouldEqual(30); form.Title.ShouldEqual("Company Holiday Party"); }
Configuration Validation
在进行对象映射的时候,有可能会出现属性名称或者自定义匹配规则不正确而又没有发现的情况,在程序执行时就报错,因此,AutoMapper提供的AssertConfigurationIsValid()方法来验证结构映射是否正确。
config.AssertConfigurationIsValid();如果映射错误,会报“AutoMapperConfigurationException”异常错误,就可以进行调试修改了
Lists and Array
AutoMapper支持的源集合类型包括:
- IEnumerable
- IEnumerable<T>
- ICollection
- ICollection<T>
- IList
- IList<T>
- List<T>
- Arrays
有一种情况是,在使用集合类型类型的时候,类型之间存在继承关系,例如下面我们需要转换的类型:
//源对象 public class ParentSource { public int Value1 { get; set; } } public class ChildSource : ParentSource { public int Value2 { get; set; } } //目标对象 public class ParentDestination { public int Value1 { get; set; } } public class ChildDestination : ParentDestination { public int Value2 { get; set; } }
AutoMapper需要孩子映射的显式配置,AutoMapper配置转换代码:
var config = new MapperConfiguration(cfg => { cfg.CreateMap<ParentSource, ParentDestination>() .Include<ChildSource, ChildDestination>(); cfg.CreateMap<ChildSource, ChildDestination>(); }); var sources = new[] { new ParentSource(), new ChildSource(), new ParentSource() }; var destinations = config.CreateMapper().Map<ParentSource[], ParentDestination[]>(sources); destinations[0].ShouldBeType<ParentDestination>(); destinations[1].ShouldBeType<ChildDestination>(); destinations[2].ShouldBeType<ParentDestination>();
注意在Initialize初始化CreateMap进行类型映射配置的时候有个Include泛型方法,签名为:“Include this configuration in derived types' maps”,大致意思为包含派生类型中配置,ChildSource是ParentSource的派生类,ChildDestination是ParentDestination的派生类,cfg.CreateMap<ParentSource, ParentDestination>().Include<ChildSource, ChildDestination>(); 这段代码是说明ParentSource和ChildSource之间存在的关系,并且要要显示的配置。
Nested mappings
嵌套对象映射,例如下面的对象:
public class OuterSource { public int Value { get; set; } public InnerSource Inner { get; set; } } public class InnerSource { public int OtherValue { get; set; } } //目标对象 public class OuterDest { public int Value { get; set; } public InnerDest Inner { get; set; } } public class InnerDest { public int OtherValue { get; set; } }
AutoMapper配置转换代码:
var config = new MapperConfiguration(cfg => { cfg.CreateMap<OuterSource, OuterDest>(); cfg.CreateMap<InnerSource, InnerDest>(); }); config.AssertConfigurationIsValid(); var source = new OuterSource { Value = 5, Inner = new InnerSource {OtherValue = 15} }; var dest = config.CreateMapper().Map<OuterSource, OuterDest>(source); dest.Value.ShouldEqual(5); dest.Inner.ShouldNotBeNull(); dest.Inner.OtherValue.ShouldEqual(15);
对于嵌套映射,只要指定下类型映射关系和嵌套类型映射关系就可以了,也就是这段代码:“Mapper.CreateMap<InnerSource, InnerDest>();” 其实我们在验证类型映射的时候加上Mapper.AssertConfigurationIsValid(); 这段代码看是不是抛出“AutoMapperMappingException”异常来判断类型映射是否正确。
参考资料
关于AutoMapper,陆续更新中...