Automapper:如何仅在子属性有映射值时才创建父对象?
问题:如何让AutoMapper仅在父对象有属性映射时才创建父对象
需要将ShoppingCart映射为Order,规则如下:
- 当购物车中存在名为"Pizza"的
Item时,Order.Pizza需被实例化,且Price取自该Item的价格 - 若购物车中无该Item,
Order.Pizza需为null,而非默认实例化的空对象
模型定义
public class ShoppingCart { public List<Item> Items { get; set; } } public class Item { public string Name { get; set; } public int Price { get; set; } } public class Order { public string Id { get; set; } public Pizza Pizza { get; set; } } public class Pizza { public int Price { get; set; } }
测试用例
[Test] public void Test_WithPizza() { // Arrange var cart = new ShoppingCart() { Items = new List<Item>() { new Item() { Name = "Cola", Price = 10 }, new Item() { Name = "Pizza", Price = 20 } } }; //Act var order = Mapper.Map<ShoppingCart, Order>(cart); //Assert Assert.IsNotNull(order); Assert.IsNotNull(order.Pizza); Assert.AreEqual(20, order.Pizza.Price); } [Test] public void Test_WithoutPizza() { // Arrange var cart = new ShoppingCart() { Items = new List<Item>() { new Item() { Name = "Cola", Price = 10 } } }; //Act var order = Mapper.Map<ShoppingCart, Order>(cart); //Assert Assert.IsNotNull(order); Assert.IsNull(order.Pizza); // 原配置此处失败 }
原配置问题
原配置通过ForPath给Pizza.Price加条件,但AutoMapper会先实例化Pizza父对象再处理属性映射,因此无法阻止空对象的创建:
public class MyProfile : Profile { public MyProfile() { CreateMap<ShoppingCart, Order>() .ForMember(dest => dest.Id, opt => opt.MapFrom(_ => Guid.NewGuid())) .ForPath(dest => dest.Pizza.Price, opt => { opt.MapFrom(src => GetPizzaPrice(src)); opt.Condition(con => GetPizzaPrice(con.Source) != null); }); } private static int? GetPizzaPrice(ShoppingCart cart) => cart.Items.SingleOrDefault(i => i.Name == "Pizza")?.Price; }
解决方案
方法1:直接映射Pizza对象(推荐)
直接对Order.Pizza字段配置映射逻辑,通过ResolveUsing控制是否创建实例:
public class MyProfile : Profile { public MyProfile() { CreateMap<ShoppingCart, Order>() .ForMember(dest => dest.Id, opt => opt.MapFrom(_ => Guid.NewGuid().ToString())) .ForMember(dest => dest.Pizza, opt => opt.ResolveUsing(src => { var pizzaItem = src.Items.SingleOrDefault(i => i.Name == "Pizza"); return pizzaItem != null ? new Pizza { Price = pizzaItem.Price } : null; })); } }
此方法直接掌控Pizza对象的创建时机,逻辑清晰,完全符合需求。
方法2:条件映射父对象
先为Order.Pizza设置创建条件,再映射子属性:
public class MyProfile : Profile { public MyProfile() { CreateMap<ShoppingCart, Order>() .ForMember(dest => dest.Id, opt => opt.MapFrom(_ => Guid.NewGuid().ToString())) .ForMember(dest => dest.Pizza, opt => { // 仅当存在Pizza Item时才创建Pizza实例 opt.Condition(src => src.Items.Any(i => i.Name == "Pizza")); opt.MapFrom(src => new Pizza()); }) .ForPath(dest => dest.Pizza.Price, opt => { opt.MapFrom(src => src.Items.Single(i => i.Name == "Pizza").Price); }); } }
当条件不满足时,Order.Pizza会保持null,不会被实例化。
两种方案均可通过上述测试用例,实现需求。
内容的提问来源于stack exchange,提问作者Thorberg
相关产品推荐
相关产品推荐

