2013-10-17 4 views
5

У меня возникли проблемы с отображением моего родительского класса с использованием automapper. Учитывая следующие классы, я создал профиль сопоставления.Automapper Сопоставление базовых классов с наследованием

Mapping классы:

public class SourceClass 
{ 
    public int SourceProperty1 { get; set; } 
    public int SourceProperty2 { get; set; } 
    public string SourceProperty3 { get; set; } 
    public string SourceProperty4 { get; set; } 
} 

public class TargetBaseClass 
{ 
    public int TargetProperty1 { get; set; } 
    public int TargetProperty2 { get; set; } 
} 

public class TargetClass1: TargetBaseClass 
{ 
    public string TargetProperty3 { get; set; } 
} 

public class TargetClass2: TargetBaseClass 
{ 
    public string TargetProperty4 { get; set; } 
} 

Карта:

public class MappingProfile: Profile 
{ 
    protected override void Configure() 
    { 
     CreateMap<SourceClass, TargetBaseClass>() 
      .Include<SourceClass, TargetClass1>() 
      .Include<SourceClass, TargetClass2>() 
      .ForMember(dst => dst.TargetProperty1, opt => opt.MapFrom(src => src.SourceProperty1)) 
      .ForMember(dst => dst.TargetProperty2, opt => opt.MapFrom(src => src.SourceProperty2)); 
     CreateMap<SourceClass, TargetClass1>() 
      .ForMember(dst => dst.TargetProperty3, opt => opt.MapFrom(src => src.SourceProperty3)); 
     CreateMap<SourceClass, TargetClass2>() 
      .ForMember(dst => dst.TargetProperty4, opt => opt.MapFrom(src => src.SourceProperty4)); 
    } 
} 

И наконец моя программа:

static void Main(string[] args) 
{ 
    Mapper.Initialize(x => x.AddProfile<MappingProfile>()); 
    var sourceClass = new SourceClass 
     { 
      SourceProperty1 = 1, 
      SourceProperty2 = 2, 
      SourceProperty3 = "3", 
      SourceProperty4 = "4" 
     }; 
    var targetBaseClass = Mapper.Map<TargetBaseClass>(sourceClass); 
    var targetClass1 = Mapper.Map<TargetClass1>(sourceClass); 
    var targetClass2 = Mapper.Map<TargetClass2>(sourceClass); 

    Console.WriteLine("TargetBaseClass: {0} {1}", targetBaseClass.TargetProperty1, 
         targetBaseClass.TargetProperty2); //1 2 
    Console.WriteLine("TargetClass1: {0} {1} {2}", targetClass1.TargetProperty1, targetClass1.TargetProperty2, 
         targetClass1.TargetProperty3);//0 0 3 ??? 
    Console.WriteLine("TargetClass2: {0} {1} {2}", targetClass2.TargetProperty1, targetClass2.TargetProperty2, 
         targetClass2.TargetProperty4);//1 2 4 
} 

к веро lem, когда я пытаюсь сопоставить производные классы, свойства родительского класса не будут отображаться в случае TargetClass1, но он будет для TargetClass2. Может ли кто-нибудь объяснить мне, что я делаю неправильно, и почему эти две карты действуют по-другому? (Имеет ли порядок, в котором я Include материя?)

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

Редактировать 2: Основываясь на комментарии @GruffBunny, я думаю, что смогу «исправить» это с помощью метода расширения. Тем не менее, я не понимаю, почему они это сделали. Глядя на коде AutoMapper.TypeMap, я могу ясно видеть это:

public void IncludeDerivedTypes(Type derivedSourceType, Type derivedDestinationType) 
{ 
    _includedDerivedTypes[derivedSourceType] = derivedDestinationType; 
} 

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

+2

Посмотрите на этот [вопрос] (http://stackoverflow.com/questions/13994081/automapper-inheritance-mapping-not-working-same-source-multiple-destination), который охватывает ваш сценарий. –

+0

@GruffBunny Спасибо, я нашел нечто похожее в вопросе [this] (http://stackoverflow.com/questions/14705064/mapping-one-source-class-to-multiple-derived-classes-with-automapper). Однако я не совсем уверен, является ли это ошибкой или по дизайну. – Kippie

ответ

5

Вы можете посмотреть на это custom extension method. Исходный код также можно найти на Github here.

Метод расширения имеет древовидные минусы, о которых я могу думать прямо сейчас. Во-первых, Mapper.AssertConfigurationIsValid() потерпит неудачу, потому что он не найдет сопоставления свойств, определенные в базовой карте. Решением этого будет игнорирование любого из предоставленных сопоставлений элементов, определенных в базовой карте.

Во-вторых, метод расширения зависит от статического класса Mapper. Если вы используете AutoMapper, тогда проблем нет. Если у вас есть несколько механизмов сопоставления и/или код записи для интерфейсов AutoMapper, вы не можете использовать этот метод расширения. Для поддержки обоих случаев нам нужно добавить два необязательных аргумента: IConfigurationProvider и IMappingEngine.

Я стараюсь избегать использования статического класса Mapper и вводить интерфейсы там, где они мне нужны, через контейнер IoC.

Третье, что метод расширения не возвращает IMappingExpression<TSource, TDestination> и запрещает переопределять сопоставления базовых элементов. Чтобы исправить это, мы возвращаем IMappingExpression<TSource, TDestination> и удаляем условия для всех членов.

Это приводит к следующему коду:

public enum WithBaseFor 
{ 
    Source, 
    Destination, 
    Both 
} 

public static class AutoMapperExtensions 
{ 
    public static IMappingExpression<TSource, TDestination> InheritMappingFromBaseType<TSource, TDestination>(
     this IMappingExpression<TSource, TDestination> mappingExpression, 
     WithBaseFor baseFor = WithBaseFor.Both, 
     IMappingEngine mappingEngine = null, 
     IConfigurationProvider configurationProvider = null) 
    { 
     Type sourceType = typeof (TSource); 
     Type destinationType = typeof (TDestination); 

     Type sourceParentType = baseFor == WithBaseFor.Both || baseFor == WithBaseFor.Source 
      ? sourceType.BaseType 
      : sourceType; 

     Type destinationParentType = baseFor == WithBaseFor.Both || baseFor == WithBaseFor.Destination 
      ? destinationType.BaseType 
      : destinationType; 

     mappingExpression 
      .BeforeMap((sourceObject, destObject) => 
      { 
       if (mappingEngine != null) 
        mappingEngine.Map(sourceObject, destObject, sourceParentType, destinationParentType); 
       else 
        Mapper.Map(sourceObject, destObject, sourceParentType, destinationParentType); 
      }); 

     TypeMap baseTypeMap = configurationProvider != null 
      ? configurationProvider.FindTypeMapFor(sourceParentType, destinationParentType) 
      : Mapper.FindTypeMapFor(sourceParentType, destinationParentType); 

     if (baseTypeMap == null) 
     { 
      throw new InvalidOperationException(
       string.Format("Missing map from {0} to {1}.", new object[] 
       { 
        sourceParentType.Name, 
        destinationParentType.Name 
       })); 
     } 

     foreach (PropertyMap propertyMap in baseTypeMap.GetPropertyMaps()) 
      mappingExpression.ForMember(propertyMap.DestinationProperty.Name, opt => opt.Ignore()); 

     return mappingExpression; 
    } 
} 

Использование

CreateMap<SourceClass, TargetBaseClass>() 
    .ForMember(dst => dst.TargetProperty1, opt => opt.MapFrom(src => src.SourceProperty1)) 
    .ForMember(dst => dst.TargetProperty2, opt => opt.MapFrom(src => src.SourceProperty2)); 

CreateMap<SourceClass, TargetClass1>() 
    .ForMember(dst => dst.TargetProperty3, opt => opt.MapFrom(src => src.SourceProperty3)) 
    .InheritMappingFromBaseType(WithBaseFor.Destination) 
    ; 

CreateMap<SourceClass, TargetClass2>() 
    .ForMember(dst => dst.TargetProperty4, opt => opt.MapFrom(src => src.SourceProperty4)) 
    .InheritMappingFromBaseType(WithBaseFor.Destination) 
    ; 

Может быть, есть еще какой-то сценарий, который не покрыт, но это, безусловно, исправляет проблему и таким образом, вы не необходимо написать конкретные методы расширения.

+0

Nice и особенно * многоразовый * разрез. Это избавит меня от необходимости писать новый класс расширения каждый раз, когда мне нужно что-то делать в этом направлении. Код выглядит чистым, хотя мне понадобилось немного времени, чтобы полностью понять. благодаря – Kippie

3

Я закончил создание метода расширения способом, описанным here и here.

public static class Extensions 
{ 
    public static IMappingExpression<SourceClass, TDestination> MapBase<TDestination>(
     this IMappingExpression<Source, TDestination> mapping) 
     where TDestination: TargetBaseClass 
    { 
     // all base class mappings goes here 
     return mapping 
      .ForMember(dst => dst.TargetProperty1, opt => opt.MapFrom(src => src.SourceProperty1)) 
      .ForMember(dst => dst.TargetProperty2, opt => opt.MapFrom(src => src.SourceProperty2)); 
    } 
} 

Я до сих пор не знаю, почему this line не позволяет нескольких типов должны быть указаны внутри IDictionary<type, IEnumerable<type>> хотя. Я уверен, что у людей в AutoMapper есть свои причины.

Смежные вопросы