简体   繁体   中英

Automapper Many-to-Many using DTOs

This is my first real attempt using Automapper and I'm struggling to properly map a many-to-many relationship using DTOs.

Here are the models:

public class Camp
{
    [Key]
    public long Id { get; set; }

    [Required]
    [MaxLength( 150 )]
    public string Name { get; set; }

    [Required]
    [MaxLength( 150 )]
    public string Location { get; set; }       

    [Required]
    public DateTime StartDate { get; set; }

    [NotMapped]
    public int CampYear
    {
        get => StartDate.Year;
    }    
    public bool Archived { get; set; }    
    public ICollection<Application> Applications { get; set; }    
    public ICollection<CampStaffPosition> CampStaffPositions { get; set; }
}

public class StaffPosition
{
    [Key]
    public int Id { get; set; }
    public string PositionName { get; set; }    
    public ICollection<CampStaffPosition> CampStaffPositions { get; set; }
}

public class CampStaffPosition
{
    public long CampId { get; set; }    
    public Camp Camp { get; set; }    
    public int StaffPositionId { get; set; }    
    public StaffPosition StaffPosition { get; set; }    
    public short PositionQuantity { get; set; } // Additional Info
}

And the DTOs I'm trying to map to:

public class CampDto
{
    public long Id { get; set; }    
    public string Name { get; set; }    
    public string Location { get; set; }    
    public DateTime StartDate { get; set; }    
    public int CampYear { get; }    
    public bool Archived { get; set; }    
    public ICollection<ApplicationDto> Applications { get; set; }    
    public ICollection<StaffPositionDto> Positions { get; set; } // Through CampStaffPositions
}

public class StaffPositionDto
{
    public int Id { get; set; }    
    public string Type { get; set; }    
    public string PositionName { get; set; }    
    public short PositionQuantity { get; set; } // From CampStaffPositions
}

After reading several of the other SO posts and trying to follow their examples, I've come up short. Here are a couple different mapping attempts:

    CreateMap<Camp, CampDto>()
        .ForMember( d => d.Positions, opt => opt.MapFrom( d => d.CampStaffPositions.Select( d => d.StaffPosition ).ToList() ) );

    CreateMap<StaffPosition, CampDto>()
        .ForMember( pr => pr.Positions, opt => opt.MapFrom( cp => cp.PositionName ) );

    CreateMap<StaffPosition, StaffPositionDto>();

    //CreateMap<StaffPosition, StaffPositionDto>()
    //    .ForMember( cr => cr.PositionQuantity, opt => opt.MapFrom( c => c.CampStaffPositions ) );

These are the most recent errors that I'm getting (with the commented line included):

Unable to create a map expression from StaffPosition.CampStaffPositions (System.Collections.Generic.ICollection`1[Server.Models.CampStaffPosition]) to StaffPositionDto.PositionQuantity (System.Int16) 
Mapping types: StaffPosition -> StaffPositionDto Server.Models.StaffPosition -> Shared.Dto.Core.StaffPositionDto 
Type Map configuration: StaffPosition -> StaffPositionDto Server.Models.StaffPosition -> Shared.Dto.Core.StaffPositionDto Destination Member: PositionQuantity

and with the commented line excluded:

Expression of type 'System.Collections.Generic.List`1[Server.Models.StaffPosition]' cannot be used for parameter of type 'System.Linq.IQueryable`1[Server.Models.StaffPosition]' of method 'System.Linq.IQueryable`1[Shared.Dto.Core.StaffPositionDto] Select[StaffPosition,StaffPositionDto](System.Linq.IQueryable`1[Server.Models.StaffPosition], System.Linq.Expressions.Expression`1[System.Func`2[Server.Models.StaffPosition,Shared.Dto.Core.StaffPositionDto]])'

How can I map the many-to-many to include the additional property from the join table without having to include the join table in my DTOs?

You need to flatten a complex object. You have properties in child objects, which you want to bring up one level higher, while still leveraging AutoMapper mapping capabilities. There is a method called IncludeMembers() (see the docs ) that exists precisely for such case. It allows you to reuse the configuration in the existing maps for the child types, that way PositionName will be included from a child object StaffPosition acting as a second source when mapping from CampStaffPosition to StaffPositionDto :

config.CreateMap<Camp, CampDto>()
    .ForMember(d => d.Positions, o => o.MapFrom(s => s.CampStaffPositions));
config.CreateMap<StaffPosition, StaffPositionDto>();
config.CreateMap<CampStaffPosition, StaffPositionDto>()
    .IncludeMembers(p => p.StaffPosition);
config.CreateMap<Application, ApplicationDto>();

Usage:

var result = mapper.Map<List<CampDto>>(campsFromDatabase);

or using ProjectTo() :

var result = await dbContext
    .Set<Camp>()
    .ProjectTo<CampDto>(mapper.ConfigurationProvider)
    .ToListAsync();

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM