2010-07-27 18 views
34

tener este escenario:AutoMapper y herencia - Cómo hacer un mapa?

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

tengo una IList<Base> mis mapas son:

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! 

Pero las propiedades que se encuentran en claseA O ClassB no se asignan cuando hago esto:

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

¿Cómo puedo hacer para mapear propiedades que están definidas en ClasA y ClassB

Respuesta

64

Usted tendrá que crear clases DTO que responden a su clases de dominio de esta manera:

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

este caso es necesario cambiar sus asignaciones a esto:

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

     Mapper.CreateMap<ClassA, DTO_A>(); 

     Mapper.CreateMap<ClassB, DTO_B>(); 

     Mapper.AssertConfigurationIsValid(); 

Una vez hecho esto, entonces el siguiente funcionará:

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

Lamentablemente, esto significa que tiene un elenco no deseado, b Pero no creo que haya mucho que puedas hacer al respecto.

+0

Gracias! Buena solución – Gringo

+0

No te preocupes Gringo, me alegro de que haya ayudado. – Simon

0

Hice esto para resolver el problema

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

Debe haber una mejor manera de hacer esto.

2

Al menos con las últimas versiones AutoMapper (?> 2.0) su código está bien si se quita el IList<>: s de su primera declaración CreateMap . Y no tiene que crear clases de DTO específicas como sugiere @Simon en otra respuesta (a menos que eso sea lo que desea).

Pero para ser específico acerca de la herencia y evitar las cláusulas de asignación redundantes al extender la clase base, puede especificar la herencia mediante el método .Include. Por lo tanto, si crea las asignaciones de la siguiente manera:

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! 

entonces usted puede hacer esto:

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

(Tenga en cuenta que usted no tiene que asignar IList específicamente AutoMapper se encarga de esto para usted.).
See this article acerca de .Include.

En realidad, me pregunto si el código compilado como está escrito en la pregunta?

+0

Hola. Gracias por tu respuesta. Sí, compila – Gringo

+0

Esto debería ser una respuesta válida para el mapeo de herencia. ¡Gracias! –

0

Para su escenario, debe usar el método IMappingExpression.ConvertUsing. Al usarlo, puede proporcionar el tipo apropiado para el objeto recién creado. Por favor, mira a mi ejemplo (bastante bien adaptarlas a su entorno):

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; } 
    } 
} 
1

A raíz de la respuesta de Eugene Gorbovoy, si está utilizando perfiles para configurar su AutoMapper, es necesario utilizar un TypeConverter.

Crear un nuevo TypeConverter como esto

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

y reemplazar la línea ConvertUsing en su ejemplo, con

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