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
Ответы
Ответ 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; }
}
}