Search code examples
c#entity-framework-coreautomapper

How to map related data with AutoMapper


I'm trying to map three entity models into one viewmodel using AutoMapper. The final output should be a recursive category tree with products in the categories. The category tree is working, but the Products-property of the viewmodel is null. My query is returning categories and products, so I'm thinking that the mapping doesn't know how to map the products to the viewmodel.

My entity models:

public class ProductCategory
{
    public int Id { get; set; }
    public int SortOrder { get; set; }
    public string Title { get; set; }
    [ForeignKey(nameof(ParentCategory))]
    public int? ParentId { get; set; }
    // Nav.props:
    public ProductCategory ParentCategory { get; set; }
    public ICollection<ProductCategory> Children { get; set; }
    public List<ProductInCategory> ProductInCategory { get; set; }
}

public class ProductInCategory
{
    public int Id { get; set; }
    public int ProductId { get; set; }
    public int SortOrder { get; set; }
    public int ProductCategoryId { get; set; }
    public bool IsProductCategoryFrontPage { get; set; }
    // Nav.props.
    public Product Product { get; set; }
    public ProductCategory ProductCategory { get; set; }
}

public class Product
{
    public int Id { get; set; }
    public string Title { get; set; }
    public string Info { get; set; }
    public decimal Price { get; set; }
    // Nav.prop:
    public List<ProductInCategory> InCategories { get; set; }
}

My viewmodel:

public class ViewModelProductCategory
{
    public int Id { get; set; }
    public int? ParentId { get; set; }
    public string Title { get; set; }
    public int SortOrder { get; set; }

    public string ProductCountInfo
    {
        get
        {
            return Products != null && Products.Any() ? Products.Count().ToString() : "0";
        }
    }

    public ViewModelProductCategory ParentCategory { get; set; }
    public IEnumerable<ViewModelProductCategory> Children { get; set; }
    public IEnumerable<ViewModelProduct> Products { get; set; }
}

I have tried this mapping (maps categories, but no products):

CreateMap<ProductCategory, ViewModelProductCategory>();
CreateMap<ViewModelProductCategory, ProductCategory>();

I have tried this mapping (maps categories, but no products):

CreateMap<ProductCategory, ViewModelProductCategory>()
    .ForMember(dto => dto.Id, opt => opt.MapFrom(src => src.Id))
    .ForMember(dto => dto.ParentId, opt => opt.MapFrom(src => src.ParentId))
    .ForMember(dto => dto.Title, opt => opt.MapFrom(src => src.Title))
    .ForMember(dto => dto.SortOrder, opt => opt.MapFrom(src => src.SortOrder))
    .ForMember(dto => dto.Children, opt => opt.MapFrom(src => src.Children))
    .ForMember(dto => dto.Products, opt => opt.MapFrom(src => src.ProductInCategory));

Solution

  • Looks like you want to skip the junction (link) entity in the view model.

    To do that, first create a mapping from Product to ViewModelProduct and then define a mapping from ProductCategory.ProductInCategory to ViewModelProductCategory.Products using projection converting List<ProductInCategory> to IEnumerable<Product>. AutoMapper will take care converting IEnumerable<Product> to List<ViewModelProduct> the same way as if you were querying Products:

    CreateMap<Product, ViewModelProduct>();
    
    CreateMap<ProductCategory, ViewModelProductCategory>()
        .ForMember(dst => dst.Products, opt => opt.MapFrom(
            src => src.ProductInCategory.Select(pc => pc.Product)));