Warm tip: This article is reproduced from serverfault.com, please click

AutoMapper third level subcollection map with root properties

发布于 2020-11-28 06:41:27

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

    }

Edit

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));
                
            

        });
Questioner
user3907939
Viewed
0
Xerillio 2020-11-28 19:11:00

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.)