1

I have a single Model class in my Business Logic, Pet class.

In this class I have a discriminator property called Type (int = 1, 2, 3, ...)

The final mapping must be Dto of specific derived classes.

I make use of ConstructUsing but it goes on Stack Overflow Exception since it has a recursion on base type mapping rule.

The derived Dto classes are correctly mapped since they have no recursions.

Also tried PreserveReferences() without luck

using AutoMapper;
using System;
using System.Collections.Generic;

namespace ConsoleAppMapper
{
    class Program
    {
        static void Main(string[] args)
        {
            var mapper = new MapperConfiguration(cfg =>
            {
                cfg.CreateMap<Pet, Dto.Pet>()
                    .PreserveReferences()
                    .ForMember(dst => dst.Name, opt => opt.MapFrom(src => src.PetName))
                    .ConstructUsing((src, context) =>
                    {
                        switch (src.Type)
                        {
                            case 1: return context.Mapper.Map<Pet, Dto.Dog>(src);
                            case 2: return context.Mapper.Map<Pet, Dto.Cat>(src);
                            case 3: return context.Mapper.Map<Pet, Dto.Mouse>(src);
                            default: return context.Mapper.Map<Pet, Dto.Pet>(src);
                        }
                    })
                ;
                cfg.CreateMap<Pet, Dto.Dog>();
                cfg.CreateMap<Pet, Dto.Cat>();
                cfg.CreateMap<Pet, Dto.Mouse>();
            }).CreateMapper();

            var pets = new List<Pet>
            {
                new Pet { PetName = "Bob", Type = 1 },
                new Pet { PetName = "Tom", Type = 2 },
                new Pet { PetName = "Jerry", Type = 3 },
                new Pet { PetName = "Duffy", Type = 4 },
            };
            var dtoList = mapper.Map<IEnumerable<Pet>, IEnumerable<Dto.Pet>>(pets);
        }
    }

    public class Pet
    {
        public string PetName;
        public int Type;
    }
}

namespace Dto
{
    public class Pet
    {
        public string Name;
    }

    public class Dog : Pet
    {
    }

    public class Cat : Pet
    {
    }

    public class Mouse : Pet
    {
    }
}

Update: With this version it seems to work properly

cfg.CreateMap<Pet, Dto.Pet>()
    .ForMember(dst => dst.Name, opt => opt.MapFrom(src => src.PetName))
    .ConstructUsing((src, context) =>
    {
        switch (src.Type)
        {
            case 1: return context.Mapper.Map<Pet, Dto.Dog>(src);
            case 2: return context.Mapper.Map<Pet, Dto.Cat>(src);
            case 3: return context.Mapper.Map<Pet, Dto.Mouse>(src);

            default: return context.Mapper.Map(src, new Dto.Pet { }, context);
        }
    })
;
cfg.CreateMap<Pet, Dto.Dog>();
cfg.CreateMap<Pet, Dto.Cat>();
cfg.CreateMap<Pet, Dto.Mouse>();
  • It seems that a Map method overload did the trick, but I am not sure this is the right way of doing what I want. `default: return context.Mapper.Map(src, new Dto.Pet { }, context);` – Marco Scavarda Feb 03 '19 at 01:01
  • Yes, you have to manually pass the context. This was changed with https://github.com/AutoMapper/AutoMapper/issues/2937 so it's no longer required. Try the [MyGet](https://docs.automapper.org/en/stable/The-MyGet-build.html) build. – Lucian Bargaoanu Feb 03 '19 at 04:51
  • How would the last myGet version fix this behaviour? I've tried last MyGet build but this is not the point. What it doesn't work is `default: return context.Mapper.Map(src);`. What it works is `default: return context.Mapper.Map(src, new Dto.Pet { });` – Marco Scavarda Feb 03 '19 at 06:39
  • Maybe it's only a reference issue? – Marco Scavarda Feb 03 '19 at 06:45
  • As I've already said, what changed is that you don't have to explicitly pass the context anymore. You need to have the same context to avoid SO. – Lucian Bargaoanu Feb 03 '19 at 07:51
  • Even if it is the same context, won't the `default: return context.Mapper.Map(src);` case still be called recursively? Which is what causes the SO. Maybe something like a conditional `ConstructUsing` would solve this, but that doesn't seem to exist – devNull Feb 03 '19 at 14:26
  • https://stackoverflow.com/questions/48824263/automapper-what-is-the-difference-between-preservereferences-and-maxdepth – Lucian Bargaoanu Feb 03 '19 at 16:44
  • Passing the context is actually the only way to avoid SO in recursive mapping inside the CostructorUsing. This https://stackoverflow.com/questions/51314718/automapper-inheritance-preserve-reference/51322881 helped me also – Marco Scavarda Feb 03 '19 at 22:14

1 Answers1

0

This is my full solution, it covers all mapping combinations

using AutoMapper;
using System;
using System.Collections.Generic;

namespace ConsoleAppMapper
{
    class Program
    {
        static void Main(string[] args)
        {
            var mapper = new MapperConfiguration(cfg =>
            {
                cfg.CreateMap<Pet, Dto.Pet>()
                    .Include<Pet, Dto.Dog>()
                    .Include<Pet, Dto.Cat>()
                    .Include<Pet, Dto.Mouse>()

                    .ForMember(dst => dst.Name, opt => opt.MapFrom(src => src.PetName))
                    .ForMember(dst => dst.Description, opt => opt.Ignore())

                    .ConstructUsing((src, context) =>
                    {
                        switch (src.Type)
                        {
                            case 1: return context.Mapper.Map(src, new Dto.Dog { }, context);
                            case 2: return context.Mapper.Map(src, new Dto.Cat { }, context);
                            case 3: return context.Mapper.Map(src, new Dto.Mouse { }, context);
                            default: return context.Mapper.Map(src, new Dto.Pet { }, context);
                        }
                    })
                ;

                cfg.CreateMap<Pet, Dto.Dog>()
                    .ForMember(dst => dst.Description, opt => opt.MapFrom(src => "This is a dog"))
                ;

                cfg.CreateMap<Pet, Dto.Cat>()
                    .ForMember(dst => dst.Description, opt => opt.MapFrom(src => "This is a cat"))
                ;

                cfg.CreateMap<Pet, Dto.Mouse>()
                    .ForMember(dst => dst.Description, opt => opt.MapFrom(src => "This is a mouse"))
                ;

            }).CreateMapper();

            // Test
            var pets = new List<Pet>
            {
                new Pet { PetName = "Bob", Type = 1 },
                new Pet { PetName = "Tom", Type = 2 },
                new Pet { PetName = "Jerry", Type = 3 },
                new Pet { PetName = "Duffy", Type = 4 },
            };

            // Full mixed collection
            var dtoList = mapper.Map<IEnumerable<Pet>, IEnumerable<Dto.Pet>>(pets);

            // Single item
            var dog = mapper.Map<Pet, Dto.Pet>(pets[0]); 
            var dog2 = mapper.Map<Pet, Dto.Dog>(pets[0]); 
        }
    }

    public class Pet
    {
        public string PetName;
        public int Type;
    }
}

namespace Dto
{
    public class Pet
    {
        public string Name;
        public string Description;
    }

    public class Dog : Pet
    {
    }

    public class Cat : Pet
    {
    }

    public class Mouse : Pet
    {
    }
}