web-dev-qa-db-fra.com

AutoMapper convertit à partir de plusieurs sources

Disons que j'ai deux classes de modèles:

public class People {
   public string FirstName {get;set;}
   public string LastName {get;set;}
}

Ayez également un téléphone de classe:

public class Phone {
   public string Number {get;set;}
}

Et je veux convertir en un PeoplePhoneDpour ceci:

public class PeoplePhoneDto {
    public string FirstName {get;set;}
    public string LastName {get;set;}
    public string PhoneNumber {get;set;}
}

Disons que dans mon contrôleur, j'ai:

var people = repository.GetPeople(1);
var phone = repository.GetPhone(4);

// normally, without automapper I would made
return new PeoplePhoneDto(people, phone) ;

Je n'arrive pas à trouver d'exemple pour ce scénario. Est-ce possible ?

Remarque: l'exemple n'est pas réel, juste pour cette question.

57
Bart Calixto

Vous ne pouvez pas directement mapper de nombreuses sources vers une seule destination - vous devez appliquer les cartes une par une, comme décrit dans la réponse Andrew Whitaker . Vous devez donc définir tous les mappages:

Mapper.CreateMap<People, PeoplePhoneDto>();
Mapper.CreateMap<Phone, PeoplePhoneDto>()
        .ForMember(d => d.PhoneNumber, a => a.MapFrom(s => s.Number));

Créez ensuite l'objet de destination par l'un de ces mappages et appliquez d'autres mappages à l'objet créé. Et cette étape peut être simplifiée avec une méthode d'extension très simple:

public static TDestination Map<TSource, TDestination>(
    this TDestination destination, TSource source)
{
    return Mapper.Map(source, destination);
}

L'utilisation est très simple:

var dto = Mapper.Map<PeoplePhoneDto>(people)
                .Map(phone);
87
Sergey Berezovskiy

Vous pouvez utiliser un Tuple pour cela:

Mapper.CreateMap<Tuple<People, Phone>, PeoplePhoneDto>()
    .ForMember(d => d.FirstName, opt => opt.MapFrom(s => s.Item1.FirstName))
    .ForMember(d => d.LastName, opt => opt.MapFrom(s => s.Item1.LastName))
    .ForMember(d => d.Number, opt => opt.MapFrom(s => s.Item2.Number ));

Dans le cas où vous auriez plus de modèles source, vous pouvez utiliser une représentation différente (liste, dictionnaire ou autre) qui rassemblera tous ces modèles en tant que source.

Le code ci-dessus doit de préférence être placé dans un fichier AutoMapperConfiguration, défini une fois et globalement, puis utilisé le cas échéant.

Par défaut, AutoMapper ne prend en charge qu'une seule source de données. Il n'y a donc pas de possibilité de définir directement plusieurs sources (sans les regrouper dans une collection) car alors comment saurions-nous ce qui se passerait si, par exemple, deux modèles de sources ont des propriétés avec les mêmes noms?

Il existe cependant quelques solutions pour y parvenir:

public static class EntityMapper
{
    public static T Map<T>(params object[] sources) where T : class
    {
        if (!sources.Any())
        {
            return default(T);
        }

        var initialSource = sources[0];

        var mappingResult = Map<T>(initialSource);

        // Now map the remaining source objects
        if (sources.Count() > 1)
        {
            Map(mappingResult, sources.Skip(1).ToArray());
        }

        return mappingResult;
    }

    private static void Map(object destination, params object[] sources)
    {
        if (!sources.Any())
        {
            return;
        }

        var destinationType = destination.GetType();

        foreach (var source in sources)
        {
            var sourceType = source.GetType();
            Mapper.Map(source, destination, sourceType, destinationType);
        }
    }

    private static T Map<T>(object source) where T : class
    {
        var destinationType = typeof(T);
        var sourceType = source.GetType();

        var mappingResult = Mapper.Map(source, sourceType, destinationType);

        return mappingResult as T;
    }
}

Et alors:

var peoplePhoneDto = EntityMapper.Map<PeoplePhoneDto>(people, phone);

Mais pour être tout à fait honnête, même si j'utilise AutoMapper depuis déjà quelques années, je n'ai jamais eu besoin d'utiliser le mappage à partir de plusieurs sources. Dans les cas où, par exemple, j'avais besoin de plusieurs modèles commerciaux dans mon modèle de vue unique, j'ai simplement intégré ces modèles dans la classe de modèle de vue.

Donc, dans votre cas, cela ressemblerait à ceci:

public class PeoplePhoneDto {
    public People People { get; set; }
    public Phone Phone { get; set; }
}
17
Paweł Bejger