Search code examples
entity-framework.net-coreautomapperdto

Nested mapping with entityFramework Automapper .Net Core


Here is my query with Entity Framework :

var transportlist = await _context.Set<Transport>()
       .Include(transport => transport.TransportState)
       .Select(transport =>
           new
           {
               Transport = _mapper.Map<TransportDto>(transport),
               TransportState = _mapper.Map<TransportStateForTransportDto>(transport.TransportState),
           }
        )
        .Take(limitNumber)
        .ToListAsync();

 return transportlist;

Here is my TransportDto :

public class TransportDto
{
    public int TransportId { get; set; }
    public TransportStateForTransportDto TransportState { get; set; }
    public bool Locked { get; set; }
}

Here is my TransportStateForTransportDto :

public class TransportStateForTransportDto
{
    public string Label { get; set; }
}

I would like to have something like that as result :

[
    {
        "transportId": 123456,
        "transportState" : {
            "Label": "Deleted"
        },
        "locked" : false
    }
]

But, I have this one (and it is a bit logical...) :

[
    {
        "transport": {
            "transportId": 123456,
            "transportState" : {
                "Label": "Deleted"
             },
             "locked" : false
        },
        "transportstate": {
            "Label": "Deleted"
        }           
    }
]

I thought about doing like this :

var transportlist = await _context.Set<Transport>()
       .Include(transport => transport.TransportState)
       .Select(transport =>
           new
           {
               _mapper.Map<TransportDto>(transport),
           }
        )
        .Take(limitNumber)
        .ToListAsync();

 return transportlist;

But, if I do, the property transportstate in JSON equals NULL.

So I am trying to do nested mapping where Transport should be mapped with its object properties mapped too. Note that all the properties have exactly the same name.

I wrote a Profile for mapping which is :

public class TransportsProfile : Profile
    {
        public TransportsProfile()
        {
            CreateMap<TransportState, TransportStateForTransportDto>();
            CreateMap<Transport, TransportDto>();
        }
    }

Thank you in advance for your help.


Solution

  • If you are using Automapper 8 or above you should leverage ProjectTo rather than Map to translate entities into DTOs.

    var transportlist = await _context.Set<Transport>()
        .ProjectTo<TransportDto>(_mapper.ConfigurationProvider)
        .Take(limitNumber)
        .ToListAsync();
    

    Edit: had _mapper.Configuration above, looks like the config is provided by ConfigurationProvider.

    Given that your TransportDTO contains a reference to the related TransportState DTO, automapper should be able to tell EF exactly what fields are needed from the related entities to ensure that they are loaded properly. The issue with Map is that this is really geared towards loaded objects, not expressions that EF could otherwise turn into SQL.

    With using Take you should also have an Order By clause to ensure the items returned are predictable when more than that limit are present.

    Like using Select for projection, there is no need to eager load any of the related details or disable lazy loading. EF will simply compose a query to pull the required fields provided your DTOs are purely simple data containers and don't contain references to entities within them.