Home > Software design >  OData IQueryable DTO mapping in separate method does not work
OData IQueryable DTO mapping in separate method does not work

Time:07-22

I have issues with DTO mapping. I'm using OData WebAPI and I need to use IQueryable due to paging, sorting, filtering... When I use this code (simplified for this purpose) in my WebAPI controller, it works

return Ok(_dataService.GetEntities(idUser).Select(e => new EntityDTO 
{
    ID = e.ID,
    Name = e.Name   
}));

but when I have separate method for DTO mapping it does not work.

return Ok(_dataService.GetEntities(idUser).Select(e => _dataService.MapToEntityDTO(e)));

Methods in my _dataService object (simplified)

public IQueryable<Entity> GetEntities(long idUser)
{        
    return from z in _context.Entities select z;
}

public EntityDTO MapToEntityDTO(Entity entity) {
    return new EntityDTO {
            ID = entity.ID,
            Name = entity.Name  
        };
}

Could you please someone explain me what is wrong with that ? Thanks for help.

CodePudding user response:

It would appear that GetEntities is returning an IQueryable that EF would be deferring execution until the results need to be materialized. This would fail if you try calling some arbitrary C# method (MapToEntity) because EF cannot translate that down into SQL.

What you should consider using is Automapper which contains a method called ProjectTo which can integrate with EF's IQueryable to project your DTOs through a configured mapper.

var dtos = _dataService.GetEntities(idUser)
    .ProjectTo<EntityDTO>(config)
    .ToList();
return Ok(dtos);

Where "config" is an instance of the Automapper Configuration class containing the mapping config to convert the Entity(ies) to DTO(s).

The alternative with a custom mapper that doesn't integrate with IQueryable is that you would have to materialize the entities first, then perform the mapping in memory:

var dtos = _dataService.GetEntities(idUser)
    .ToList() // Materializes the entities
    .Select(e => _dataService.MapToEntityDTO(e))
    .ToList();
return Ok(dtos);

The disadvantages of this approach are that it requires more memory and time from the server to load the entities into memory first, then perform the mapping Select. This also requires that the GetEntities() method ensures that any/all related entities that might be mapped are eager loaded, otherwise they would either trigger lazy loads or be left #null. This can blow out time and memory usage where little of this related data might actually be needed.

With the ProjectTo projection, the queries will automatically fetch whatever related details are needed without the overhead of needing to eager load relations or tripping lazy loads.

  • Related