- 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));
Skip
ping 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
});
}