I am trying to map a source sub collection to a destination sub collection which work. But I also need to map some base properties on the sub collection. I tried with IncludeMembers but no luck there. I tried to find some example that map two types to one but that doesn't seems to apply to my case because most example are converting two instance of different type to one when mine is all nested in the same object.
I prepared a unit test to show my problem :
Objects
public class FlatProduct
{
public string Sku { get; set; }
public int BrandId { get; set; }
public int CategoryId { get; set; }
public int ProductVersionId { get; set; }
public int PriceLevelId { get; set; }
public decimal Price { get; set; }
public decimal RebatePrice { get; set; }
public List<FlatProductInventory> FlatProductInventory { get; set; }
}
public class FlatProductInventory
{
public int StockedLocationId { get; set; }
public int StockedQty { get; set; }
}
public class Product
{
public string Sku { get; set; }
public int BrandId { get; set; }
public int CategoryId { get; set; }
public ProductVersion ProductVersion { get; set; }
}
public class ProductVersion
{
public string Sku { get; set; }
public int BrandId { get; set; }
public int CategoryId { get; set; }
public int ProductVersionId { get; set; }
public ProductPrice ProductPrice { get; set; }
public List<ProductInventory> ProductInventory { get; set; }
}
public class ProductPrice
{
public string Sku { get; set; }
public int BrandId { get; set; }
public int CategoryId { get; set; }
public int ProductVersionId { get; set; }
public int PriceLevelId { get; set; }
public decimal Price { get; set; }
public decimal RebatePrice { get; set; }
}
public class ProductInventory
{
public string Sku { get; set; }
public int BrandId { get; set; }
public int CategoryId { get; set; }
public int ProductVersionId { get; set; }
public int StockedLocationId { get; set; }
public int StockedQty { get; set; }
}
Mapping/Test
public static void TestMapper()
{
var config = new MapperConfiguration(cfg => {
cfg.CreateMap<FlatProduct, ProductPrice>();
cfg.CreateMap<FlatProduct, ProductVersion>();
cfg.CreateMap<FlatProduct, ProductInventory>();
cfg.CreateMap<FlatProductInventory, ProductInventory>();
cfg.CreateMap<FlatProduct, ProductVersion>()
.ForMember(productVersion => productVersion.ProductPrice, flatProduct => flatProduct.MapFrom(fp => fp))
.ForMember(productVersion => productVersion.ProductInventory, flatProduct => flatProduct.MapFrom(fp => fp))
.ForMember(productVersion => productVersion.ProductInventory, flatProduct => flatProduct.MapFrom(fp => fp.FlatProductInventory));
cfg.CreateMap<FlatProduct, Product>()
.ForMember(product => product.ProductVersion, flatProduct => flatProduct.MapFrom(fp => fp));
});
IMapper mapper = config.CreateMapper();
var flatProductExample = new FlatProduct
{
Sku = "ABC123",
BrandId = 1,
CategoryId = 1,
ProductVersionId = 1,
PriceLevelId = 1,
Price = 12.99m,
RebatePrice = 9.99m,
FlatProductInventory = new List<FlatProductInventory>()
{
new FlatProductInventory()
{
StockedLocationId = 1,
StockedQty = 33
}
}
};
var mappedProduct = mapper.Map<Product>(flatProductExample);
Assert.Equal(flatProductExample.Sku, mappedProduct.ProductVersion.ProductInventory[0].Sku); //Fail
Assert.Equal(flatProductExample.FlatProductInventory[0].StockedQty, mappedProduct.ProductVersion.ProductInventory[0].StockedQty); //Pass
}
Xerillio was really close to the answer I needed and in fact his answer will work for version under 8 of automapper. In my case I am using the latest version so I had to modify his code a little to make it work. Here it is:
var config = new MapperConfiguration(cfg => {
cfg.CreateMap<FlatProduct, ProductPrice>();
cfg.CreateMap<FlatProduct, ProductVersion>();
cfg.CreateMap<FlatProduct, ProductInventory>();
cfg.CreateMap<FlatProductInventory, ProductInventory>();
cfg.CreateMap<FlatProduct, ProductVersion>()
.ForMember(productVersion => productVersion.ProductPrice, flatProduct => flatProduct.MapFrom(fp => fp))
.ForMember(productVersion => productVersion.ProductInventory, opt => opt.MapFrom((flatProduct, productVersion, i, context) => {
return flatProduct.FlatProductInventory
// Map using '<FlatProductInventory, ProductInventory>'
.Select(flatPInv => context.Mapper.Map<FlatProductInventory, ProductInventory>(flatPInv))
// Map (in-place by passing in the destination object)
// using '<FlatProduct, ProductInventory>'
.Select(destPInv => context.Mapper.Map<FlatProduct, ProductInventory>(flatProduct, destPInv))
// Now you've combined the mappings from FlatProduct and FlatProductInventory
.ToList();
}));
cfg.CreateMap<FlatProduct, Product>()
.ForMember(product => product.ProductVersion, flatProduct => flatProduct.MapFrom(fp => fp));
});
My understanding is that for every FlatProductInventory
you want to have a ProductInventory
. However, the ProductInventory
also requires populating properties that come from FlatProduct
.
You were actually already on the right track with "map two types to one", so one way to solve the problem would be the following:
var config = new MapperConfiguration(cfg => {
cfg.CreateMap<FlatProduct, ProductPrice>();
cfg.CreateMap<FlatProduct, ProductVersion>();
cfg.CreateMap<FlatProduct, ProductInventory>();
cfg.CreateMap<FlatProductInventory, ProductInventory>();
cfg.CreateMap<FlatProduct, ProductVersion>()
.ForMember(productVersion => productVersion.ProductPrice, flatProduct => flatProduct.MapFrom(fp => fp))
.ForMember(productVersion => productVersion.ProductInventory,
opt => opt.ResolveUsing((flatProduct, productVersion, i, context) => {
return flatProduct.FlatProductInventory
// Map using '<FlatProductInventory, ProductInventory>'
.Select(flatPInv => context.Mapper.Map<FlatProductInventory, ProductInventory>(flatPInv))
// Map (in-place by passing in the destination object)
// using '<FlatProduct, ProductInventory>'
.Select(destPInv => context.Mapper.Map<FlatProduct, ProductInventory>(flatProduct, destPInv))
// Now you've combined the mappings from FlatProduct and FlatProductInventory
.ToList();
})
);
cfg.CreateMap<FlatProduct, Product>()
.ForMember(product => product.ProductVersion, flatProduct => flatProduct.MapFrom(fp => fp));
});
(There may be syntax errors as I haven't run it through an IDE.)
Thank you you definitely pointed me in the right direction. It didn't work as is because resolve using does not exist in AutoMapper 8. I had to modify my code to make it work but the syntax was good. Thanks a lot