Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

AutoMapper: One-to-many -> Many-to-many

I'm fairly new to AutoMapper. I have a hard time configuring AutoMapper to being able to map UserViewModel with multiple TagViewModel into a many-to-many relationship (RecipeEntity <-> TagEntity) which is needed for Entity Framework Core where UserAndTagEntity is the joining table.

Data Objects:

public class TagEntity
{
    public string Name { get; set; }

    public virtual ICollection<UserAndTagEntity> UserAndTags { get; set; } = new List<UserAndTagEntity>();
}

public class UserEntity
{
    public string Name { get; set; }

    public virtual ICollection<UserAndTagEntity> UserAndTags { get; set; } = new List<UserAndTagEntity>();
}

public class UserAndTagEntity
{
    public int Id { get; set; }
    public virtual UserEntity User { get; set; }
    public virtual TagEntity Tag { get; set; }
}

public class UserViewModel
{
    public string Name { get; set; }

    public IList<TagViewModel> Tags { get; set; }
}

public class TagViewModel
{
    public string Name { get; set; }
}

Test Example:

var config = new MapperConfiguration(cfg => {
    cfg.CreateMap<UserEntity, UserViewModel>()
        .ForMember(
            dto => dto.Tags,
            opt => opt.MapFrom(x => x.UserAndTags.Select(y => y.Tag)));

    cfg.CreateMap<UserViewModel, UserEntity>()
        .ForMember(
            dto => dto.UserAndTags,
            opt => opt.MapFrom(x => x.Tags))
        .AfterMap((model, entity) =>
        {
            foreach (var entityUserAndTag in entity.UserAndTags)
            {
                entityUserAndTag.User = entity;
            }
        });

    cfg.CreateMap<TagViewModel, UserAndTagEntity>();
});

var user = new UserViewModel()
{
    Name = "User",
    Tags = new List<TagViewModel>
    {
        new TagViewModel {Name = "Tag 1"},
        new TagViewModel {Name = "Tag 2"},
        new TagViewModel {Name = "Tag 3"},
        new TagViewModel {Name = "Tag 4"},
    }
};

IMapper mapper = config.CreateMapper();

var map = mapper.Map<UserViewModel, UserEntity>(user);

This works partly - The issue I've is that Tag on UserAndTagEntity is null.

like image 729
SOK Avatar asked Jan 02 '23 16:01

SOK


1 Answers

The mapping from UserViewModel to UserEntity can be achieved with the following configuration:

CreateMap<UserViewModel, UserEntity>()
    // (1)
    .ForMember(entity => entity.UserAndTags, opt => opt.MapFrom(model => model.Tags))
    // (5)
    .AfterMap((model, entity) =>
    {
        foreach (var entityUserAndTag in entity.UserAndTags)
        {
            entityUserAndTag.User = entity;
        }
    });

// (2)
CreateMap<TagViewModel, UserAndTagEntity>()
    // (3)
    .ForMember(entity => entity.Tag, opt => opt.MapFrom(model => model));

// (4)
CreateMap<TagViewModel, TagEntity>();

Explanation:

The line (1) is needed because the target and source property names do not match, so we just tell AutoMapper to map Tags property of the UserViewModel to UserAndTags property of the UserEntity.

Note that the mapping does not require that the source and target property types match. If they don't (as in this case), AutoMapper will map them using a separate configuration.

In our case, the source property type is IList<TagViewModel> and target property type is ICollection<UserAndTagEntity>. Ignore the collection types - AutoMapper knows how to convert them. What it doesn't know and needs to be specified is the mapping between the element types. In our case, from TagViewModel to UserAndTagEntity. Hence the need of mapping (2).

Inside the mapping (2), we have only one of the parts, so we use (3) to specify that - i.e. we map TagViewModel to Tag property of the UserAndTagEntity. Again the types do not match, so we need the mapping (4) from TagViewModel to TagEntity.

With all that the final result will be UserEntity instance with UserAndTags collection populated with UserAndTagEntity instances having correct Tag property. Then the step (5) is used to populate the User property of these instances.

like image 101
Ivan Stoev Avatar answered Jan 13 '23 00:01

Ivan Stoev