AutoMapper 展平(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; }
}
我们希望将这个复杂的 Order
对象展平为一个更简单的只包含特定场景所需数据的 OrderDto
:
public class OrderDto
{
public string CustomerName { get; set; }
public decimal Total { get; set; }
}
在 AutoMapper
中配置源类型和目标类型对时,配置器会尝试将源类型上的属性和方法与目标类型上的属性进行匹配。如果目标类型上任何属性在源类型上没有对应的属性、方法或以 "Get"
开头的方法,则 AutoMapper
会根据 PascalCase
约定将目标成员名拆分成单个单词。
// 复杂模型示例
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);
// 配置AutoMapper
var configuration = new MapperConfiguration(cfg => cfg.CreateMap<Order, OrderDto>());
// 执行映射
OrderDto dto = mapper.Map<Order, OrderDto>(order);
dto.CustomerName.ShouldBe("George Costanza");
dto.Total.ShouldBe(74.85m);
我们使用 CreateMap
方法在 AutoMapper
中配置了类型映射。AutoMapper
只能映射已知的类型对,因此我们必须显式注册源/目标类型对。进行映射时,我们使用 Map
方法。
在 OrderDto
类型中,Total
属性与 Order
上的 GetTotal()
方法相匹配。CustomerName
属性与 Order
上的 Customer.Name
属性相匹配。只要我们适当地命名目标属性,就不需要单独配置属性匹配。
如果你想禁用此行为,可以使用 ExactMatchNamingConvention
:
cfg.DestinationMemberNamingConvention = new ExactMatchNamingConvention();
IncludeMembers
如果你在展平时需要更多控制,可以使用 IncludeMembers
。当已有从子类型到目标类型的映射时(不同于经典展平,后者不需要为子类型提供映射),你可以映射子对象的成员到目标对象。
class Source
{
public string Name { get; set; }
public InnerSource InnerSource { get; set; }
public OtherInnerSource OtherInnerSource { get; set; }
}
class InnerSource
{
public string Name { get; set; }
public string Description { get; set; }
}
class OtherInnerSource
{
public string Name { get; set; }
public string Description { get; set; }
public string Title { get; set; }
}
class Destination
{
public string Name { get; set; }
public string Description { get; set; }
public string Title { get; set; }
}
cfg.CreateMap<Source, Destination>().IncludeMembers(s=>s.InnerSource, s=>s.OtherInnerSource);
cfg.CreateMap<InnerSource, Destination>(MemberList.None);
cfg.CreateMap<OtherInnerSource, Destination>();
var source = new Source { Name = "name", InnerSource = new InnerSource{ Description = "description" },
OtherInnerSource = new OtherInnerSource{ Title = "title" } };
var destination = mapper.Map<Destination>(source);
destination.Name.ShouldBe("name");
destination.Description.ShouldBe("description");
destination.Title.ShouldBe("title");
这允许你在映射父类型 Source
和 Destination
时重用子类型 InnerSource
和 OtherInnerSource
现有映射中的配置。其工作方式类似于 映射继承 ,但使用的是组合而非继承。
IncludeMembers
调用中参数的顺序很重要。当映射目标成员时,首先匹配的获胜,从源对象本身开始,然后按照你指定的顺序匹配包含的子对象。所以在上面的例子中,Name
是从源对象本身映射的,而 Description
来自 InnerSource
,因为它是第一个匹配项。
请注意,这种匹配是静态的,在配置时发生,而不是在 Map
时,所以子对象的运行时类型不被考虑。
IncludeMembers
与 ReverseMap
集成。包含的成员将被反转为
ForPath(destination => destination.IncludedMember, member => member.MapFrom(source => source))
反之亦然。如果你不希望这样,可以避免使用 ReverseMap
(显式创建反向映射)或覆盖默认设置(使用 Ignore
或无参数的 IncludeMembers
)。
详情请查看 测试代码 。