Подтвердить что ты не робот

AutoMapper и наследование - Как сделать карту?

Возьмите этот сценарий:

Public class Base {  public string Name; }

Public Class ClassA :Base {  public int32 Number;  }

Public Class ClassB :Base { Public string Description;}

Public Class DTO {
  public string Name;
  public int32 Number;
  Public string Description;
}

У меня есть IList<Base> мои карты:

AutoMapper.Mapper.CreateMap<IList<Base>, IList<DTO>>()
   .ForMember(dest => dest.Number, opt => opt.Ignore())
   .ForMember(dest => dest.Description, opt => opt.Ignore());

AutoMapper.Mapper.CreateMap<ClassA, DTo>()
   .ForMember(dest => dest.Description, opt => opt.Ignore());

AutoMapper.Mapper.CreateMap<ClassB, DTO>()
   .ForMember(dest => dest.Number, opt => opt.Ignore())

Mapper.AssertConfigurationIsValid(); //Is OK!

Но свойства, которые находятся в ClassA или ClassB, не отображаются, когда я это делаю:

IList<DTO>= AutoMapper.Mapper.Map<IList<Base>,IList<DTO>>(baseList);

Как сделать, чтобы отобразить свойства, определенные в ClasA и ClassB

4b9b3361

Ответ 1

Вам нужно будет создать классы DTO, соответствующие вашим классам домена следующим образом:

public class DTO
{
    public string Name;
}

public class DTO_A : DTO
{
    public int Number { get; set; }
}

public class DTO_B : DTO
{
    public string Description { get; set; }
}

Затем вам нужно изменить свои сопоставления на следующее:

        Mapper.CreateMap<Base, DTO>()
            .Include<ClassA, DTO_A>()
            .Include<ClassB, DTO_B>();

        Mapper.CreateMap<ClassA, DTO_A>();

        Mapper.CreateMap<ClassB, DTO_B>();

        Mapper.AssertConfigurationIsValid();

Как только это будет сделано, будет выполнено следующее:

        var baseList = new List<Base>
        {
            new Base {Name = "Base"},
            new ClassA {Name = "ClassA", Number = 1},
            new ClassB {Name = "ClassB", Description = "Desc"},
        };

        var test = Mapper.Map<IList<Base>,IList<DTO>>(baseList);
        Console.WriteLine(test[0].Name);
        Console.WriteLine(test[1].Name);
        Console.WriteLine(((DTO_A)test[1]).Number);
        Console.WriteLine(test[2].Name);
        Console.WriteLine(((DTO_B)test[2]).Description);
        Console.ReadLine();

К сожалению, это означает, что у вас есть нежелательный ролик, но я не думаю, что вы можете это сделать.

Ответ 2

По крайней мере, с недавними версиями Automapper ( > 2.0?) ваш код в порядке, если вы удалите IList<>: s вашего первого оператора CreateMap 1. И вам не нужно создавать определенные классы DTO, поскольку @Simon предлагает в другом ответе (если только это не то, что вы хотите).

Но чтобы быть конкретным в отношении наследования и избегать избыточных предложений сопоставления при расширении базового класса, вы можете указать наследование с помощью метода .Include. Итак, если вы создаете свои сопоставления следующим образом:

Mapper.CreateMap<Base, DTO>()
    .Include<ClassA, DTO>()
    .Include<ClassB, DTO>()
    .ForMember(dest => dest.Description, opt => opt.Ignore())
    .ForMember(dest => dest.Number, opt => opt.Ignore());

Mapper.CreateMap<ClassA, DTO>()
    .ForMember(dest => dest.Description, opt => opt.Ignore());

Mapper.CreateMap<ClassB, DTO>()
    .ForMember(dest => dest.Number, opt => opt.Ignore());

Mapper.AssertConfigurationIsValid(); //Is OK!

вы можете сделать это:

var baseList = new List<Base>
{
    new Base {Name = "Base"},
    new ClassA {Name = "ClassA", Number = 1},
    new ClassB {Name = "ClassB", Description = "Desc"},
};

var test = Mapper.Map<IList<Base>, IList<DTO>>(baseList);
Console.WriteLine(test[0].Name);
Console.WriteLine(test[1].Name);
Console.WriteLine((test[1]).Number);
Console.WriteLine(test[2].Name);
Console.WriteLine((test[2]).Description);
Console.ReadLine();

(Обратите внимание, что вам не нужно специально отображать IList. Automapper обрабатывает это для вас.)
См. статью о .Include.

1 На самом деле мне интересно, скомпилирован ли код, как написано в вопросе?

Ответ 3

После ответа Евгения Горбового, если вы используете профили для настройки своего автомастера, вам нужно использовать TypeConverter.

Создайте новый TypeConverter, как этот

    public class NumberConverter : ITypeConverter<DTO, NumberBase>
    {
        public NumberBase Convert(DTO source, NumberBase destination, ResolutionContext context)
        {
            if (source.Id % 2 == 0)
            {
                return context.Mapper.Map<EvenNumber>(source);
            }
            else
            {
                return context.Mapper.Map<OddNumber>(source);
            }
        }
    }

и замените строку ConvertUsing в своем примере на

  expression.CreateMap<DTO, NumberBase>()
            .ConvertUsing(new NumberConverter());

Ответ 4

Я сделал это, чтобы решить проблему

IList<DTO> list1 = AutoMapper.Mapper.Map<IList<ClassA>,IList<DTO>>(baseList.OfType<ClassA>().ToList());

IList<DTO> list2 = AutoMapper.Mapper.Map<IList<ClassB>,IList<DTO>>(baseList.OfType<ClassB>().ToList());

list = list1.Union(list2);

persons.OfType<T>().ToList()

Должен быть лучший способ сделать это.

Ответ 5

Для вашего сценария вам необходимо использовать метод IMappingExpression.ConvertUsing. С его помощью вы можете предоставить соответствующий тип для вновь созданного объекта. Пожалуйста, посмотрите мой пример (очень хорошо подходит для вашего сценария):

using System;
using System.Linq;
using AutoMapper;

namespace ConsoleApplication19
{
    internal class Program
    {
        private static void Main(string[] args)
        {
            //mapping
            Mapper.Initialize(expression =>
            {
                expression.CreateMap<DTO, NumberBase>()
                    .ForMember(@class => @class.IdOnlyInDestination,
                        configurationExpression => configurationExpression.MapFrom(dto => dto.Id))
                    .ConvertUsing(dto =>//here is the function that creates appropriate object
                    {
                        if (dto.Id%2 == 0) return Mapper.Map<EvenNumber>(dto);
                        return Mapper.Map<OddNumber>(dto);
                    });

                expression.CreateMap<DTO, OddNumber>()
                    .IncludeBase<DTO, NumberBase>();

                expression.CreateMap<DTO, EvenNumber>()
                    .IncludeBase<DTO, NumberBase>();
            });

            //initial data
            var arrayDto = Enumerable.Range(0, 10).Select(i => new DTO {Id = i}).ToArray();

            //converting
            var arrayResult = Mapper.Map<NumberBase[]>(arrayDto);

            //output
            foreach (var resultElement in arrayResult)
            {
                Console.WriteLine($"{resultElement.IdOnlyInDestination} - {resultElement.GetType().Name}");
            }

            Console.ReadLine();
        }
    }

    public class DTO
    {
        public int Id { get; set; }

        public int EvenFactor => Id%2;
    }

    public abstract class NumberBase
    {
        public int Id { get; set; }
        public int IdOnlyInDestination { get; set; }
    }

    public class OddNumber : NumberBase
    {
        public int EvenFactor { get; set; }
    }

    public class EvenNumber : NumberBase
    {
        public string EventFactor { get; set; }
    }
}