Published on

Multi-source mapping with AutoMapper using Aggregate LINQ extension method

Reading time
4 min read

Configure multiple source models to single destination model using AutoMapper

With AutoMapper it is possible to configure multiple sources to single destination like this

public class MappingProfile : Profile
{
    public MappingProfile()
    {
        CreateMap<Guid, Person>(MemberList.Destination)
            .ForMember(d => d.Id, cfg => cfg.MapFrom(s => s));

        CreateMap<PersonDto, Person>()
            .ForMember(d => d.Id, cfg => cfg.Ignore());
    }
}

It is also possible to do the mapping to create single destination object like this

var id = Guid.NewGuid();
var personDto = new PersonDto
{
    Email = "fake@email.com",
    Name = "John Doe",
    DateOfBirth = DateTimeOffset.Now.AddYears(-25)
};

var person = mapper.Map<Person>(id);
person = mapper.Map(personDto, person);

Last 2 lines above shows how to perform mapping from multiple sources to single destination model.
If you wish to map another configured source you'll have to use mapper.Map(source, destination) again.
Passing the created instance from previous Map operation as destination to the next.

Using Aggregate LINQ extension method

I learned this trick from code shared by Isaac. The above can be achieved using Aggregate LINQ extension method. Making it easier to perform multi-source mapping.

var id = Guid.NewGuid();
var personDto = new PersonDto
{
    Email = "fake@email.com",
    Name = "John Doe",
    DateOfBirth = DateTimeOffset.Now.AddYears(-25)
};
var sources = new object[] { id, personDto };
var person2 = sources.Skip(1).Aggregate(mapper.Map<Person>(sources[0]), (current, next) => mapper.Map(next, current));

Skipping the first element in sources because we are using it as seed for the Aggregate method to create a seed destination model.

This can be improved by creating an extension method for AutoMapper IMapper.

public static class AutoMapperExtensions
{
    public static T MultiSourceMap<T>(this IMapper mapper, params object?[] sources)
        => sources.Skip(1).Aggregate(mapper.Map<T>(sources[0]), (current, next) => mapper.Map(next, current));
}

And here is a quick unit test for it. This test is using FluentAssertion

[Fact]
public void MultiSourceMap_maps_multiple_sources_to_single_destination()
{
    var config = new MapperConfiguration(cfg =>
    {
        cfg.AddProfile<MappingProfile>();
    });
    var mapper = new Mapper(config);
    var id = Guid.NewGuid();
    var personDto = new PersonDto
    {
        Email = "emailme@here.com",
        Name = "Test name"
    };

    var result = mapper.MultiSourceMap<Person>(id, personDto);
    //FluentAssertion
    result.Should().NotBeNull().And.BeEquivalentTo(new Person
    {
        Id = id,
        Name = personDto.Name,
        Email = personDto.Email
    });
}