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


person Gringo    schedule 27.07.2010    source источник


Ответы (5)


Вам нужно будет создать классы 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();

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

person Simon    schedule 13.10.2010
comment
Не беспокойтесь, Гринго, рад, что это помогло. - person Simon; 15.10.2010

По крайней мере, с последними версиями Automapper (>2.0?) с вашим кодом все в порядке, если вы удалите IList<>:s вашего первого оператора CreateMap1. И вам не нужно создавать определенные классы 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На самом деле интересно, скомпилирован ли код так, как написано в вопросе?

person Ulf Åkerstedt    schedule 15.04.2014
comment
Привет. Спасибо за ответ. Да, компилируется - person Gringo; 16.04.2014
comment
Это должен быть действительно правильный ответ для сопоставления наследования. Спасибо! - person Pawel Cioch; 17.08.2016
comment
Зачем вам нужны как .Include, так и отдельные сопоставления из ClassA и ClassB? - person xr280xr; 19.12.2018

Следуя ответу Евгения Горбового, если вы используете профили для настройки AutoMapper, вам необходимо использовать файл 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());
person podiluska    schedule 22.02.2017
comment
Идеальный ответ - именно то, что я искал - person Sebastian Patten; 05.05.2021

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

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

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

person Gringo    schedule 29.07.2010

Для вашего сценария вы должны использовать метод 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; }
    }
}
person Evgeny Gorbovoy    schedule 20.07.2016