Search code examples
c#automapper

How do I map one object from two other objects when collections are involved?


I have a list<x> where x contains another object y as well as a property p. I want to map my list<x> into a list<z> where z is an object that contains all of the properties of y plus property p. In the code below, it succeeds with property p but fails to map the properties of object y.

Basically, I want to get the unit test below to pass. I'm hoping this can be done with a smarter Profile instead of requiring a foreach loop.

using AutoMapper;
using System.Collections.Generic;
using System.Linq;
using FluentAssertions;
using Xunit;

namespace Test;

public class TheProblem
{
    [Fact]
    public void ShouldMap()
    {
        // Arrange
        var mapper = new MapperConfiguration(cfg => { cfg.AddProfile<MyMappingProfile>(); }).CreateMapper();
        var inner = new InnerSource() { MyProperty1 = 1, MyProperty2 = 2 };
        var outer = new OuterSource() { Inner = inner, MyCount = 7 };
        var list = new List<OuterSource>() { outer };

        // Act
        var result = mapper.Map<IEnumerable<Destination>>(list);

        // Assert
        var dest = result.First();
        dest.MyBool.Should().BeTrue(); //passes!
        dest.MyProperty1.Should().Be(1); //fails
        //Failure Message: Expected dest.MyProperty1 to be 1, but found 0 (difference of - 1).

        dest.MyProperty2.Should().Be(2); //also fails

    }
}

public class Destination
{
    public int MyProperty1 { get; set; }
    public int MyProperty2 { get; set; }
    public bool MyBool { get; set; }
}

public class InnerSource
{
    public int MyProperty1 { get; set; }
    public int MyProperty2 { get; set; }
}

public class OuterSource
{
    public InnerSource Inner { get; set; } = null!;
    public int MyCount { get; set; }
}

public class MyMappingProfile : Profile
{
    public MyMappingProfile()
    {
        CreateMap<InnerSource, Destination>();
        CreateMap<OuterSource, Destination>()
            .ForMember(dest => dest.MyBool, opt => opt.MapFrom(src => src.MyCount > 0));
    }
}

Solution

  • The answer appears to lie in the ConstructUsing() method.

    I stumbled across the answer in one of the related questions it put at the bottom. Strangely, that thread didn't turn up in my original search for answers (I read about 10 before posting, so I thought I was thorough).

    Anyway adding this to the CreateMap solved it:

    .ConstructUsing((src, ctx) => ctx.Mapper.Map<Destination>(src.Inner))