对象映射中,经常把复杂对象扁平化为更简单的模型。例子:

  1. public class Order
  2. {
  3. private readonly IList<OrderLineItem> _orderLineItems = new List<OrderLineItem>();
  4. public Customer Customer { get; set; }
  5. public OrderLineItem[] GetOrderLineItems()
  6. {
  7. return _orderLineItems.ToArray();
  8. }
  9. public void AddOrderLineItem(Product product, int quantity)
  10. {
  11. _orderLineItems.Add(new OrderLineItem(product, quantity));
  12. }
  13. public decimal GetTotal()
  14. {
  15. return _orderLineItems.Sum(li => li.GetTotal());
  16. }
  17. }
  18. public class Product
  19. {
  20. public decimal Price { get; set; }
  21. public string Name { get; set; }
  22. }
  23. public class OrderLineItem
  24. {
  25. public OrderLineItem(Product product, int quantity)
  26. {
  27. Product = product;
  28. Quantity = quantity;
  29. }
  30. public Product Product { get; private set; }
  31. public int Quantity { get; private set;}
  32. public decimal GetTotal()
  33. {
  34. return Quantity*Product.Price;
  35. }
  36. }
  37. public class Customer
  38. {
  39. public string Name { get; set; }
  40. }

我们希望将此复杂的Order对象扁平化为一个更简单的OrderDto,其中仅包含特定场景所需的数据:

  1. public class OrderDto
  2. {
  3. public string CustomerName { get; set; }
  4. public decimal Total { get; set; }
  5. }

在AutoMapper中配置源/目标类型对时,配置程序将尝试将源类型上的属性和方法与目标类型上的属性进行匹配。如果目标类型上的任何属性的属性,方法或以“ Get”为前缀的方法在源类型上都不存在,则AutoMapper会将目标成员名称拆分为单个单词(按照PascalCase约定)。

  1. // Complex model
  2. var customer = new Customer
  3. {
  4. Name = "George Costanza"
  5. };
  6. var order = new Order
  7. {
  8. Customer = customer
  9. };
  10. var bosco = new Product
  11. {
  12. Name = "Bosco",
  13. Price = 4.99m
  14. };
  15. order.AddOrderLineItem(bosco, 15);
  16. // Configure AutoMapper
  17. var configuration = new MapperConfiguration(cfg => cfg.CreateMap<Order, OrderDto>());
  18. // Perform mapping
  19. OrderDto dto = mapper.Map<Order, OrderDto>(order);
  20. dto.CustomerName.ShouldEqual("George Costanza");
  21. dto.Total.ShouldEqual(74.85m);

我们使用CreateMap方法在AutoMapper中配置了类型映射。AutoMapper只能映射它知道的类型对,因此我们已向CreateMap显式注册了源/目标类型对。为了执行映射,我们使用Map方法。

在OrderDto类型上,Total 属性与 Order 上的 GetTota() 方法匹配。CustomerName属性与Order上的Customer.Name属性匹配。只要我们适当地命名目标属性,就不需要配置单个属性匹配。

IncludeMembers

如果在扁平化时需要更多控制,则可以使用IncludeMembers。当您已经具有从子类型到目标类型的映射时,可以将子对象的成员映射到目标对象(与经典的扁平化不同,该子类不需要映射)。

  1. class Source
  2. {
  3. public string Name { get; set; }
  4. public InnerSource InnerSource { get; set; }
  5. public OtherInnerSource OtherInnerSource { get; set; }
  6. }
  7. class InnerSource
  8. {
  9. public string Name { get; set; }
  10. public string Description { get; set; }
  11. }
  12. class OtherInnerSource
  13. {
  14. public string Name { get; set; }
  15. public string Description { get; set; }
  16. public string Title { get; set; }
  17. }
  18. class Destination
  19. {
  20. public string Name { get; set; }
  21. public string Description { get; set; }
  22. public string Title { get; set; }
  23. }
  24. cfg.CreateMap<Source, Destination>().IncludeMembers(s=>s.InnerSource, s=>s.OtherInnerSource);
  25. cfg.CreateMap<InnerSource, Destination>(MemberList.None);
  26. cfg.CreateMap<OtherInnerSource, Destination>();
  27. var source = new Source { Name = "name", InnerSource = new InnerSource{ Description = "description" },
  28. OtherInnerSource = new OtherInnerSource{ Title = "title" } };
  29. var destination = mapper.Map<Destination>(source);
  30. destination.Name.ShouldBe("name");
  31. destination.Description.ShouldBe("description");
  32. destination.Title.ShouldBe("title");

因此,这可以重用现有的地图配置为子类型InnerSource和OtherInnerSource映射父类型时Source和Destination。它的工作方式与映射继承相似,但是它使用合成,而不是继承。
IncludeMembers调用中参数的顺序是相关的。映射目标成员时,第一个匹配获胜,从源对象本身开始,然后按照您指定的顺序从包含的子对象开始。因此,在上面的示例中,Name是从源对象本身以及Description从对象进行映射的,InnerSource因为这是第一个匹配项。

IncludeMembers integrates with ReverseMap. An included member will be reversed to:

  1. ForPath(destination => destination.IncludedMember, member => member.MapFrom(source => source))

and the other way around. If that’s not what you want, you can avoid ReverseMap (explicitly create the reverse map) or you can override the default settings (using Ignore or IncludeMembers without parameters respectively).

For details, check the tests.