Scott Mitchell
Scott Mitchell

Reputation: 8759

Can AutoMapper implicitly flatten this mapping?

I am trying to map between two lists of objects. The source type has a complex property of type A; the destination type is a flattened subset of type A plus an additional scalar property that is in the source type.

public class A
{
    public int Id { get; set; }
    public string Name { get; set; }
}

public class Source
{
    public A MyA { get; set; }
    public int SomeOtherValue { get; set; }
}

public class Destination
{
    public string Name { get; set; }
    public int SomeOtherValue { get; set; }
}

If it's not clear, I'd like Source.MyA.Name to map to Destination.Name and Source.SomeOtherValue to map to Destination.SomeOtherValue.

In reality, type A has a dozen or so properties, about which 80% map over to properties of the same name in Destination. I can get things to work if I explicitly spell out the mappings in CreateMap like so:

CreateMap<Source, Destination>()
    .ForMember(dest => dest.Name, opt => opt.MapFrom(src => src.MyA.Name));

The downside here is I want to avoid having to add a ForMember line for each of A's properties that need to get copied over to Destination. I was hoping I could do something like:

CreateMap<Source, Destination>()
    .ForMember(dest => dest, opt => opt.MapFrom(src => src.MyA));

But if I try the above I get a runtime error when the mapping is registered: "Custom configuration for members is only supported for top-level individual members on a type."

Thanks

Upvotes: 4

Views: 2803

Answers (3)

Prasad Kanaparthi
Prasad Kanaparthi

Reputation: 6563

Try this,

Mapper.CreateMap<A, Destination>();
Mapper.CreateMap<Source, Destination>()
    .ForMember(destination => destination.Name, options => options.MapFrom(source => Mapper.Map<A, Destination>(source.MyA).Name));

var objSource = new Source { SomeOtherValue = 7, MyA = new A { Id = 1, Name = "SomeName" } };
var result = Mapper.Map<Source, Destination>(objSource);

Upvotes: 0

dark_ruby
dark_ruby

Reputation: 7874

create mappings between A and Destination, and Source and Destination, and then use AfterMap() to use first mapping in second

Mapper.CreateMap<A, Destination>();
Mapper.CreateMap<Source, Destination>()
      .AfterMap((s, d) => Mapper.Map<A, Destination>(s.MyA, d));

then use it like this:

var res = Mapper.Map<Source, Destination>(new Source { SomeOtherValue = 7,  MyA = new A { Id = 1, Name = "SomeName" } });

Upvotes: 5

k0stya
k0stya

Reputation: 4315

As a workaround you can use custom type converter with additional property in the destination type to avoid recursion.

[TestFixture]
public class MapComplexType
{
    [Test]
    public void Map()
    {
        Mapper.CreateMap<A, Destination>();

        Mapper.CreateMap<Source, Destination>().ConvertUsing(new TypeConvertor());
        var source = new Source
                         {
                             MyA = new A
                                       {
                                           Name = "Name"
                                       },
                                       SomeOtherValue = 5
                         };
        var dest = new Destination();
        Mapper.Map(source, dest);
        Assert.AreEqual(dest.Name, "Name");
    }
}

public class TypeConvertor : ITypeConverter<Source, Destination>
{
    public Destination Convert(ResolutionContext context)
    {
        var destination = (Destination) context.DestinationValue;
        if (!((Destination)context.DestinationValue).IsMapped || destination == null)
        {
            destination = destination ?? new Destination();
            destination.IsMapped = true; // To avoid recursion
            Mapper.Map((Source)context.SourceValue, destination);
                            destination.IsMapped = false; // If you want to map the same object few times
        }
        Mapper.Map(((Source)context.SourceValue).MyA, destination);
        return (Destination)context.DestinationValue;
    }
}

public class A
{
    public int Id { get; set; }
    public string Name { get; set; }
}

public class Source
{
    public A MyA { get; set; }
    public int SomeOtherValue { get; set; }
}

public class Destination
{
    public string Name { get; set; }
    public int SomeOtherValue { get; set; }
    // Used only for mapping purposes
    internal bool IsMapped { get; set; }
}

Upvotes: 0

Related Questions