Есть ли способ правильно использовать AutoMapper для реализации наследования с дискриминатором?

У меня есть один класс Model в моем классе Business Logic, Pet.

В этом классе у меня есть свойство дискриминатора под названием Type (int = 1, 2, 3,...)

Окончательное отображение должно быть Dto конкретных производных классов.

Я использую ConstructUsing, но возникает исключение переполнения стека, поскольку оно имеет рекурсию по правилу сопоставления базового типа.

Производные классы Dto отображаются корректно, так как у них нет рекурсий.

Также безуспешно пробовал PreserveReferences()

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
    {
    }
}

Обновление: в этой версии все работает правильно.

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>();

person Marco Scavarda    schedule 03.02.2019    source источник
comment
Кажется, что перегрузка метода Map сделала свое дело, но я не уверен, что это правильный способ сделать то, что я хочу. default: return context.Mapper.Map(src, new Dto.Pet { }, context);   -  person Marco Scavarda    schedule 03.02.2019
comment
Да, вам нужно вручную передать контекст. Это было изменено с помощью github.com/AutoMapper/AutoMapper/issues/2937, так что это не требуется дольше. Попробуйте сборку MyGet.   -  person Lucian Bargaoanu    schedule 03.02.2019
comment
Как последняя версия myGet исправит это поведение? Я пробовал последнюю сборку MyGet, но это не главное. Что не работает, так это default: return context.Mapper.Map<Pet, Dto.Pet>(src);. Это работает default: return context.Mapper.Map<Pet, Dto.Pet>(src, new Dto.Pet { });   -  person Marco Scavarda    schedule 03.02.2019
comment
Может быть, это только справочная проблема?   -  person Marco Scavarda    schedule 03.02.2019
comment
Как я уже сказал, изменилось то, что вам больше не нужно явно передавать контекст. Вам нужно иметь тот же контекст, чтобы избежать SO.   -  person Lucian Bargaoanu    schedule 03.02.2019
comment
Даже если это один и тот же контекст, не будет ли случай default: return context.Mapper.Map<Pet, Dto.Pet>(src); вызываться рекурсивно? Что и вызывает ТАК. Возможно, что-то вроде условного ConstructUsing решит эту проблему, но, похоже, этого не существует.   -  person devNull    schedule 03.02.2019
comment
stackoverflow .com/questions/48824263/   -  person Lucian Bargaoanu    schedule 03.02.2019
comment
Передача контекста на самом деле является единственным способом избежать SO при рекурсивном отображении внутри CostructorUsing. Этот stackoverflow.com/questions/51314718/ помог и я   -  person Marco Scavarda    schedule 04.02.2019


Ответы (1)


Это мое полное решение, оно охватывает все комбинации карт.

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
    {
    }
}
person Marco Scavarda    schedule 03.02.2019
comment
Спасибо Лучиану Баргаоану за поддержку. Я жду выпуска 8.1, чтобы отказаться от передачи контекста в качестве аргумента. - person Marco Scavarda; 04.02.2019