3
votes

I have this simple class

public class Store {
    public int Id { get; set; }

    //Other properties

    [JsonIgnore]
    public ICollection<Product> Products { get; set; }
}

and my DTO

public class StoreDetails {
    public int Id { get; set; }

    //Other properties

    public ICollection<Product> Products { get; set; }
}

and my Product class:

public class Product {
    public int? Id { get; set; }

    //Other properties

    public ICollection<ProductAttribute> ProductAttributes { get; set; }
}

And my Mapping looks like that:

var storeDetails = await _context.Stores
    .Include(s => s.Products)
    .ThenInclude(p => p.ProductAttributes)
    .ProjectTo<StoreDetails>(new MapperConfiguration(c => c.CreateProfile("TEST", e => {
        e.CreateMap<Store, StoreDetails>();
    })))
    .SingleOrDefaultAsync(p => p.Id == id);

Everything looks fine in the Store object, but in StoreDetails ProductAttributes is null every time.

Why AutoMapper is not Mapping mapping second level navigation property using ProjectTo?

NOTE: I'm using AutoMapper 8.1.1.

1
You don't need Include with ProjectTo. - Lucian Bargaoanu
If i see it right, Product is the same class for both so there is no need to perform any mapping on it. Did you try running it without using ThenInclude(p => p.ProductAttributes) ? - S Schulze
@SSchulze the same result without ThenInclude - Morasiu
@LucianBargaoanu Is there a way (I guess the answer is no) to force projection for equal source and destination (element) types? Looks like AssignableExpressionBinder and EnumerableExpressionBinder are hardly forcing projection of the source expression. - Ivan Stoev
Probably there's no ToList because the AssignableExpressionBinder comes before the EnumerableExpressionBinder. Changing the order would accomplish what you want, but, as I've said, it works for me without it. - Lucian Bargaoanu

1 Answers

0
votes

I think you should add virtual property to your class something like this

public class Store {
    public int Id { get; set; }

    //Other properties

    [JsonIgnore]
    public virtual ICollection<Product> Products { get; set; }
}

public class StoreDetails {
    public int Id { get; set; }

    //Other properties

    public virtual ICollection<Product> Products { get; set; }
}

Also use Profile to map like this

public class StoreProfile : Profile
{
    public StoreProfile ()
    {
        CreateMap<Store, StoreDetails>(MemberList.None).ReverseMap();
    }
}

Then register in your startup.cs

services.AddAutoMapper();

This is how you map to the profile

private readonly IMapper _mapper;

var storeDetails = await _context.Stores
    .Include(s => s.Products)
    .ThenInclude(p => p.ProductAttributes)
    .Select(
      x => new ViewModel {
        SomeObject = _mapper.Map<Store, StoreDetails>(x)
      } 
    )
    .SingleOrDefaultAsync(p => p.Id == id);

Below is the version i'm using

<PackageReference Include="AutoMapper" Version="8.0.0" />
<PackageReference Include="AutoMapper.Extensions.Microsoft.DependencyInjection" Version="6.0.0" />