web-dev-qa-db-fra.com

Utiliser AutoMapper pour aplatir un DTO

J'ai essayé d'utiliser AutoMapper pour gagner un peu de temps entre mes DTO et mes objets de domaine, mais je ne parviens pas à configurer la carte pour qu'elle fonctionne, et je commence à me demander si AutoMapper pourrait être le mauvais outil pour le travail.

Considérons cet exemple d'objets de domaine (une entité et une valeur):

public class Person
{
    public string Name { get; set; }
    public StreetAddress Address { get; set; }
}

public class StreetAddress
{
    public string Address { get; set; }
    public string City { get; set; }
    public string State { get; set; }
}

Mon DTO (à partir d'un objet Linq-to-SQL) ressemble à ceci:

public class PersonDTO
{
    public string Name { get; set; }
    public string Address { get; set; }
    public string City { get; set; }
    public string State { get; set; }
}

J'aimerais pouvoir le faire dans mon référentiel:

return Mapper.Map<PersonDTO, Person>(result);

J'ai essayé de configurer AutoMapper chaque fois que je pouvais comprendre, mais je continue à avoir la configuration générique mappage manquant ou un mappage non pris en charge error, sans détails pour me dire où j'échoue.

J'ai essayé plusieurs configurations différentes, mais en voici quelques unes:

Mapper.CreateMap<PersonDTO, Person>()
    .ForMember(dest => dest.Address, opt => opt.MapFrom(Mapper.Map<Person, Domain.StreetAddress>));

et

Mapper.CreateMap<Person, Domain.Person>()
    .ForMember(dest => dest.Address.Address1, opt => opt.MapFrom(src => src.Address))
    .ForMember(dest => dest.Address.City, opt => opt.MapFrom(src => src.City))
    .ForMember(dest => dest.Address.State, opt => opt.MapFrom(src => src.State));

J'ai lu que aplatir les objets avec AutoMapper est facile, mais sans flatterie leur n'est pas facile ... ni même possible. Quelqu'un peut-il me dire si j'essaie de faire l'impossible et si ce n'est pas ce que je fais mal?

Notez que mes objets réels sont un peu plus compliqués, il est donc possible que je laisse de côté les informations qui sont la clé de l'erreur ... si les informations que je fais sont bonnes, je peux fournir plus d'informations ou commencer à simplifier mes objets pour les tester. .

38
Josh Anderson

utilisez https://github.com/omuleanu/ValueInjecter , cela fait aplatissement et unflattening , et tout ce dont vous avez besoin, il ya unexemple d'application asp.net mvc dans le téléchargementoù toutes les fonctionnalités sont démontrées (ainsi que les tests unitaires)

8
Omu

Cela semble aussi fonctionner pour moi:

Mapper.CreateMap<PersonDto, Address>();
Mapper.CreateMap<PersonDto, Person>()
        .ForMember(dest => dest.Address, opt => opt.MapFrom( src => src )));

Fondamentalement, créez un mappage entre le dto et les deux objets, puis utilisez-le comme source pour l'objet enfant.

62
sydneyos

En plus de sydneyos répondre et selon le commentaire de Trevor de Koekkoek, une cartographie bidirectionnelle est possible de cette façon

public class Person
{
    public string Name { get; set; }
    public Address Address { get; set; }
}

public class Address
{
    public string Street { get; set; }
    public string City { get; set; }
    public string State { get; set; }
}

public class PersonViewModel
{
    public string Name { get; set; }
    public string AddressStreet { get; set; }
    public string AddressCity { get; set; }
    public string AddressState { get; set; }
}

Mappages Automapper

Mapper.Initialize(cfg => cfg.RecognizePrefixes("Address"));
Mapper.CreateMap<Person, PersonViewModel>();
Mapper.CreateMap<PersonViewModel, Address>();
Mapper.CreateMap<PersonViewModel, Person>()
    .ForMember(dest => dest.Address, opt => opt.MapFrom( src => src )));

Si vous implémentez NameOf class, vous pouvez vous débarrasser du préfixe chaîne magique.

Mapper.Initialize(cfg => cfg.RecognizePrefixes(Nameof<Person>.Property(x => x.Address)));

EDIT: En C # 6

Mapper.Initialize(cfg => cfg.RecognizePrefixes(nameof(Person.Address)));
9
sanjuro

Impossible de poster un commentaire, donc poster une réponse. Je suppose qu'il y a eu quelques changements dans l'implémentation d'AutoMapper, donc answer https://stackoverflow.com/a/5154321/2164198 proposé par HansoS n'est plus compilable. Bien qu'il existe une autre méthode qui peut être utilisée dans de tels scénarios - ResolveUsing:

Mapper.CreateMap<Person, Domain.Person>()
    .ForMember(dest => dest.Address, opt => opt.ResolveUsing( src => { return new Address() {Address1 = src.Address, City = src.City, State = src.State }; }))
9
Ivan Samygin

Cela peut être en retard, mais vous pouvez le résoudre en utilisant des expressions lambda pour créer l'objet comme ceci:

Mapper.CreateMap<Person, Domain.Person>()
        .ForMember(dest => dest.Address, opt => opt.MapFrom( src => { return new Address() {Address1 = src.Address, City = src.City, State = src.State }; }))
5
HansoS

J'utilise ceci

public static void Unflatten<TSource, TDestination, TMember>(this IMemberConfigurationExpression<TSource, TDestination, TMember> opt)
{
    var prefix = opt.DestinationMember.Name;
    var memberProps = typeof(TMember).GetProperties();
    var props = typeof(TSource).GetProperties().Where(p => p.Name.StartsWith(prefix))
        .Select(sourceProp => new
        {
            SourceProp = sourceProp,
            MemberProp = memberProps.FirstOrDefault(memberProp => prefix + memberProp.Name == sourceProp.Name)
        })
        .Where(x => x.MemberProp != null);
    var parameter = Expression.Parameter(typeof(TSource));

    var bindings = props.Select(prop => Expression.Bind(prop.MemberProp, Expression.Property(parameter, prop.SourceProp)));
    var resolver = Expression.Lambda<Func<TSource, TMember>>(
        Expression.MemberInit(Expression.New(typeof(TMember)), bindings),
        parameter);

    opt.ResolveUsing(resolver.Compile());
}

Configuration

new MapperConfiguration(cfg =>
{
    cfg.CreateMap<Person, PersonDTO>();
    cfg.CreateMap<PersonDTO, Person>().ForMember(x => x.HomeAddress, opt => opt.Unflatten());
});

Des modèles

public class Person
{
    public string Name { get; set; }
    public Address HomeAddress { get; set; }
}

public class Address
{
    public string Line1 { get; set; }
    public string Line2 { get; set; }
    public string City { get; set; }
    public string State { get; set; }
    public string ZipCode { get; set; }
}

Suivre les conventions d'aplatissement d'AutoMapper

public class PersonDTO
{
    public string Name { get; set; }
    public string HomeAddressLine1 { get; set; }
    public string HomeAddressLine2 { get; set; }
    public string HomeAddressCity { get; set; }
    public string HomeAddressState { get; set; }
    public string HomeAddressZipCode { get; set; }
}

A probablement besoin de nombreuses améliorations mais cela fonctionne ...

1
andres.chort

J'ai une autre solution. L'idée principale est que AutoMapper savoir comment aplatir les objets imbriqués lorsque vous nommez correctement les propriétés d'un objet aplati: ajoutez le nom de la propriété de l'objet imbriqué en tant que préfixe. Pour votre cas Adresse est le préfixe:

public class PersonDTO
{
    public string Name { get; set; }
    public string AddressCity { get; set; }
    public string AddressState { get; set; }
    ...
}

Donc, créer un mappage familier de imbriqué à aplati et puis en utilisant la méthode ReverseMap permet à AutomMapper de comprendre comment effacer un objet imbriqué .

CreateMap<Person, PersonDTO>()
   .ReverseMap();

C'est tout!

0
Andrei