Search code examples
c#asp.net-coreentity-framework-coreautomapperasp.net-core-5.0

How to automap a property that is on a many to many table to retrieve properties


I'm working on a .NET 5 API. I have to reply to a GET call with JSON data that serializes the UnitDto class and inside it the list of all the InstDto class, but I need a property that resides on the UnitInst object (the table many to many).

My classes:

public class Unit
{
    public long Id { get; set; }
    public string Name { get; set; }
    public string Description { get; set; }

    public virtual ICollection<UnitInst> UnitInsts { get; set; }
}

public class Inst
{
    public long Id { get; set; }
    public string Name { get; set; }

    public virtual ICollection<UnitInst> UnitInsts { get; set; }
}

public class UnitInst
{
    public long Id { get; set; }
    public long UnitId { get; set; }
    public virtual Unit Unit { get; set; }
    public long InstId { get; set; }
    public virtual Inst Inst { get; set; }
    public string IPv4 { get; set; } // the property that is important
}

My DTOs:

public class UnitDto
{
    public long Id { get; set; }
    public string Name { get; set; }
    public string Description { get; set; }

    public IEnumerable<InstDTO> Insts { get; set; }
}

public class InstDTO
{
    public long Id { get; set; }
    public string Name { get; set; }
    public string IPv4 { get; set; } // I need serialize this property in my response json
}

When I map in this way I can't retrieve the IPv4 property from the UnitInst class (the many to many table).

CreateMap<Unit, UnitDto>()
    .ForMember(dto => dto.Insts, opt => opt.MapFrom(x => x.UnitInsts.Select(y => y.Inst).ToList()))
    .PreserveReferences();

How can I solve this problem?


Solution

  • Normally you would create 2 maps (Unit -> UnitDto and Inst -> InstDto) and use the Select trick you've shown. But that is applicable only when the join entity has no additional data, which is not the case here.

    So you need to map the join entity collection directly:

    CreateMap<Unit, UnitDto>()
        .ForMember(dst => dst.Insts, opt => opt.MapFrom(src => src.UnitInsts)); // <-- no Select
    

    and create additional map UnitInst -> InstDto:

    cfg.CreateMap<UnitInst, InstDTO>()
        .IncludeMembers(src => src.Inst) // needs `Inst` -> `InstDTO` map
        .ForMember(dst => dst.Id, opt => opt.MapFrom(src => src.Inst.Id));
    

    Here AutoMapper IncludeMembers is used to map the Inst members specified by the regular Inst -> InstDTO map, and the target Id property is mapped explicitly because both source and "included" objects have a property with the same name, in which case the source have priority, but you want Id to be Inst.Id or InstId.

    Finally the Inst -> InstDTO map:

    CreateMap<Inst, InstDTO>()
        .ForMember(dst => dst.IPv4, opt => opt.Ignore());