0

I have a configuration that does a reverse map to unflatten the source object into a destination object, however, if the source object property is read-only it is automatically ignored and I have to manually map it.

Some of my source objects have up to 100 fields, creating manual mappings like this feels cumbersome and I'm unsure why this behavior exists in auto mapper.

See below code snippet for a working example of what I am describing.

using System;
using AutoMapper;

namespace automappertests
{
    class Source
    {
        public int Field1Suffix1 { get; set; }
        public int Field1Suffix2 { get; set; }
        public int Field2Suffix1 { get; set; }

        // !!Attention!!
        // This is a readonly property with a private setter
        public int Field2Suffix2 => Field2Suffix1; 
    }

    class Destination
    {
        public Wrapper Field1 { get; set; }
        public Wrapper Field2 { get; set; }
    }

    class Wrapper
    {
        public int Suffix1 { get; set; }
        public int Suffix2 { get; set; }

        public static Wrapper New(int suffix1, int suffix2) =>
            new Wrapper
            {
                Suffix1 = suffix1,
                Suffix2 = suffix2
            };
    }

    class DestinationProfile : Profile
    {
        public DestinationProfile()
        {
            CreateMap<Destination, Source>()
                .ReverseMap()
                // !!Attention!!
                // Why do I need to have an explicit ForMember MapFrom for the readonly property?
                .ForMember(m => m.Field2, o => o.MapFrom(src => Wrapper.New(src.Field2Suffix1, src.Field2Suffix2)));
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            var config = new MapperConfiguration(cfg => { cfg.AddProfile<DestinationProfile>(); });
            var mapper = config.CreateMapper();
            var source = new Source()
            {
                Field1Suffix1 = 1,
                Field1Suffix2 = 2,
                Field2Suffix1 = 3,
            };
            var destination = mapper.Map<Source, Destination>(source);
            Console.WriteLine($"Field1.Suffix1 = {destination.Field1.Suffix1}");
            Console.WriteLine($"Field1.Suffix2 = {destination.Field1.Suffix2}");
            Console.WriteLine($"Field2.Suffix1 = {destination.Field2.Suffix1}");
            Console.WriteLine($"Field2.Suffix2 = {destination.Field2.Suffix2}");
            Console.WriteLine("Press any key to continue.");
            Console.ReadKey();
        }
    }
}
Output without .ForMember:
Field1.Suffix1 = 1
Field1.Suffix2 = 2
Field2.Suffix1 = 3
Field2.Suffix2 = 0
Output with .ForMember:
Field1.Suffix1 = 1
Field1.Suffix2 = 2
Field2.Suffix1 = 3
Field2.Suffix2 = 3
paul-shuvo
  • 1,874
  • 4
  • 33
  • 37

1 Answers1

0

Actually you have no setter. Add an explicit private setter.

public int Field2Suffix2 { get => Field2Suffix1; private set => Field2Suffix1 = value; }
Lucian Bargaoanu
  • 3,336
  • 3
  • 14
  • 19
  • There is no setter by design. Why would I need a setter on the source model in order for it to be mapped correctly? –  Jan 21 '20 at 09:31
  • Check [the execution plan](https://docs.automapper.org/en/latest/Understanding-your-mapping.html). – Lucian Bargaoanu Jan 21 '20 at 09:42
  • The actual mapping we want is `Source -> Destination` If that is all we wanted then no setter is required. However, we want to unflatten. To unflatten, we need to do a ReverseMap. This results in us creating a mapping profile `Destination -> Source` This mapping, however, does not have a setter on `Source` which is why the property is ignored. This is why the `.ForMember` is required. –  Jan 31 '20 at 17:13
  • 1
    Well, yes :), there is nothing to reverse. And having a private setter is the elegant way to solve it. – Lucian Bargaoanu Jan 31 '20 at 17:39