Home > Software design >  AutoMapper How to select Collection's property, project it into DTO & return projected collecti
AutoMapper How to select Collection's property, project it into DTO & return projected collecti

Time:07-22

Source models:

public class ExampleModel
{
    public int Id { get; set; }
    public float Something { get; set; }
    public string Info { get; set; } = "";
}

public class ExampleModelContainer
{
    public int Id { get; set; }
    public ExampleModel Model { get; set; } = null!;
}

public class LargeEntity
{
    public int Id { get; set; }
    public List<ExampleModelContainer> ModelEntries { get; set; } = null!;
}

Target models:

public class ExampleModelDto
{
    public float Something { get; set; }
    public string Info { get; set; } = "";
}

public class LargeEntityDto
{
    public int Id { get; set; }
    public List<ExampleModelDto> Models { get; set; } = null!;
}

AutoMapper profile:

public class AutoMapperProfile : Profile
{
    public AutoMapperProfile()
    {
        CreateMap<ExampleModel, ExampleModelDto>();
        
        // One of my attempt, I thought converting container into model, and automatically example model into DTO. Failed
        CreateMap<ExampleModelContainer, ExampleModel>()
            .ConvertUsing(x => x.Model);
        
        CreateMap<LargeEntity, LargeEntityDto>()
            .ForMember(x => x.Models, 
                opt => opt.MapFrom(y => y.ModelEntries));
    }
}

Expected result: LargeEntities' List<ExampleModelContainer> collection is mapped into List<ExampleModelDto>.

Actual result:

Unhandled exception. AutoMapper.AutoMapperConfigurationException: The following member on AutoMapperExps.Models.LargeEntityDto cannot be mapped:
        Models
Add a custom mapping expression, ignore, add a custom resolver, or modify the destination type AutoMapperExps.Models.LargeEntityDto.
Context:
        Mapping to member Models from AutoMapperExps.Models.LargeEntity to AutoMapperExps.Models.LargeEntityDto
Exception of type 'AutoMapper.AutoMapperConfigurationException' was thrown.
   at AutoMapper.Configuration.ConfigurationValidator.AssertConfigurationIsValid(IEnumerable`1 typeMaps)
   at AutoMapper.Configuration.ConfigurationValidator.AssertConfigurationExpressionIsValid(IEnumerable`1 typeMaps)
   at AutoMapper.MapperConfiguration.AssertConfigurationIsValid()
   at Program.Main(String[] args) in D:\Experiments\AutoMapperExps\Program.cs:line 1

How I see it possible to be made:

  1. Mapper takes source List<ExampleModelContainer> collection
  2. Mapper Selects Model property from each element of collection
  3. Mapper projects ExampleModel (returned from property above) into ExampleModelDto
  4. Mapper returns List<ExampleModelDto> as a final result.

Note: This is made for Entity Framework queries. I want to do most projection operations on server-side.

CodePudding user response:

TLDR: AutoMapper throws the exception because it cannot find a mapping ExampleModelContainer -> ExampleDto. If you define a mapping the error is fixed, e.g.:

CreateMap<ExampleModelContainer, ExampleModelDto>()
  .IncludeMembers(x => x.Model);

The mapping tries to take several steps at once. If I understand your sample right, the configured mappings are like this:

ExampleModel          -> ExampleDto
ExampleModelContainer -> ExampleModel
LargeEntity           -> LargeEntityDto

When mapping the Models property in for LargeEntityDto, you are trying to map a List<ExampleModelContainer> without further configuration to a List<ExampleModelDto>. It may seem clear for AutoMapper to take the route

ExampleModelContainer -> ExampleModel -> ExampleDto

in this case, because there is only one path. But what if there were several paths to get from a ExampleModelContainer to a ExampleDto? The library would not be able to make a decision that solves all possible combinations.

This means that you as a developer have to make the decision by configuring a mapping from ExampleModelContainer to ExampleModelDto, e.g.:

CreateMap<ExampleModelContainer, ExampleModelDto>()
  .IncludeMembers(x => x.Model);

This way, AutoMapper has a direct mapping between the types and is able to map the properties. See this fiddle to test.

  • Related