web-dev-qa-db-fra.com

AutoMapper et héritage - Comment mapper?

Ayez ce scénario:

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

J'ai un IList<Base> mes cartes sont:

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!

Mais les propriétés qui sont dans ClassA ou ClassB ne sont pas mappées lorsque je fais cela:

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

Comment puis-je faire pour mapper des propriétés définies dans ClasA et ClassB

38
Gringo

Vous devrez créer des classes DTO qui correspondent à vos classes de domaine comme ceci:

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

Vous devez ensuite modifier vos mappages comme suit:

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

        Mapper.CreateMap<ClassA, DTO_A>();

        Mapper.CreateMap<ClassB, DTO_B>();

        Mapper.AssertConfigurationIsValid();

Une fois cela fait, les éléments suivants fonctionneront:

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

Malheureusement, cela signifie que vous avez un casting indésirable, mais je ne pense pas que vous puissiez faire grand-chose à ce sujet.

76
Simon

Au moins avec les versions récentes d'Automapper (> 2.0?), Votre code est correct si vous supprimez le IList<>: s de votre première instruction CreateMap1. Et vous n'avez pas à créer de classes DTO spécifiques comme le suggère @Simon dans une autre réponse (sauf si c'est ce que vous voulez).

Mais pour être précis sur l'héritage et éviter les clauses de mappage redondantes lorsque vous étendez la classe de base, vous pouvez spécifier l'héritage en utilisant le .Include méthode. Donc, si vous créez vos mappages comme ceci:

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!

alors vous pouvez le faire:

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

(Notez que vous n'avez pas à mapper IList spécifiquement. Automapper gère cela pour vous.)
Voir cet article à propos de .Include.

1En fait, je me demande si le code compilé comme écrit dans la question?

4
Ulf Åkerstedt

Dans le prolongement de la réponse d'Eugene Gorbovoy, si vous utilisez des profils pour configurer votre AutoMapper, vous devez utiliser un TypeConverter.

Créez un nouveau TypeConverter comme celui-ci

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

et remplacez la ligne ConvertUsing dans son exemple par

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

J'ai fait ça pour résoudre le problème

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

Ça doit être une meilleure façon de faire ça.

0
Gringo

Pour votre scénario, vous devez utiliser la méthode IMappingExpression.ConvertUsing. En l'utilisant, vous pouvez fournir le type approprié pour l'objet nouvellement créé. S'il vous plaît, regardez mon exemple (correspond assez bien à votre scénario):

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; }
    }
}
0
Eugene Gorbovoy