Search code examples
c#entity-framework-coreautomapper

Using Automapper to Map M:M Relationships in EF Core


I have the following two classes:

public class ADto 
{
    public int Id { get; set; }
    public List<BDto>? BSet { get; set; }
}

public class A 
{
    public int Id { get; set; }
    public virtual ICollection<B>? BSet { get; set; }
}

I use Automapper to convert the incoming ADto to A, then create A.

AutoMapper mapping profile:

CreateMap<A, ADto>();
CreateMap<B, BDto>();

Code:

public async Task<OperationResultDto> CreateAsync(A aDto, CancellationToken cancellationToken)
{
    // Create & Map the record
    A a = new();
    mapper.Map(aDto, a);
        
    // Create the object in database
    await repository.CreateAsync(a, cancellationToken);
}   

I get the following error:

SqlException: Cannot insert explicit value for identity column in table 'base_qamarks' when IDENTITY_INSERT is set to OFF.

Now I can solve this issue by doing the following in AutoMapper:

CreateMap<A, ADto>()
      .ForMember(dest => dest.BSet, opt => opt.Ignore());

Then the manual assignment:

public async Task<OperationResultDto> CreateAsync(A aDto, CancellationToken cancellationToken)
{
    // Create & Map the record
    A a = new();
    mapper.Map(aDto, a);
    
    // Manually assign it
    foreach (BDto bDto in A.BSet)
    {
        B? b = await bRepository.GetEntityAsync(bDto.Id, cancellationToken);

        if (b != null)
        {
            a.BSet.Add(b);
        }
    }
            
    // Create the object in database
    await repository.CreateAsync(a, cancellationToken);
}   

This works, but it is additional code and doesn't look good. How do I properly use AtuoMapper to map this?


Solution

  • I was able to resolve it... using a resolver:

    public class AResolver : IValueResolver<ADto, A, ICollection<A>>
    {
        private readonly IARepository aRepository;
    
        public CompanyRoleResolver(IARepository aRepository)
        {
            this.aRepository = aRepository;
        }
    
        public ICollection<A> Resolve(ADto source, A destination, ICollection<A> destMember, ResolutionContext context)
        {
            List<A> aSet = new();
    
            if (source.ASet != null)
            {
                foreach(var aDto in source.aSet)
                {
                    A? a = aRepository.GetEntityAsync(a?.Id ?? -1, CancellationToken.None).Result;
                    if (a != null)
                    {
                        aSet.Add(a);
                    }
                }
            }
            return aSet;
        }
    }
    

    Then in the mapping profile:

    CreateMap<ADto, A>().ForMember(dest => dest.ASet, opt => opt?.MapFrom<AResolver>())
    

    But you need to register the resolver:

    builder.Services.AddSingleton<AResolver>();