web-dev-qa-db-fra.com

Comment utiliser AutoMapper pour mapper un objet de destination avec un objet enfant dans l'objet source?

J'ai les objets source et destination comme ceci:

class ProductWithCategories // Source class
{
    public Product Product { get; set; } // Product is an EF entity class
    public IEnumerable<Category> Categories { get; set; }
}

class ProductViewModel // Dest class
{
    public int Id { get; set; }
    // Other properties with the same name as Product class

    public IEnumerable<CategoryViewModel> Categories { get; set; }
}

Donc, mon besoin est de mapper les valeurs de source.Product dans dest, puis source.Categories en dest.Categories. Est-ce possible avec AutoMapper?

J'ai essayé cela et je n'ai pas été surpris de l'échec:

        config.CreateMap<ProductWithCategories, ProductViewModel>()
            .ForMember(q => q, option => option.MapFrom(q => q.Product))
            .ForMember(q => q.Categories, option => option.MapFrom(q => q.Categories));

Voici l'exception que j'ai reçue:

[AutoMapperConfigurationException: la configuration personnalisée des membres n'est prise en charge que pour les membres individuels de niveau supérieur sur un type.]

12
Luke Vo

Après quelques discussions avec OP, il s'avère que son principal besoin est de mapper rapidement un objet enfant/imbriqué à l'intérieur de l'objet source vers l'objet de destination aplati. Il ne veut pas écrire de mappage pour chaque propriété de la destination.

Voici un moyen d'y parvenir:

  • Définir un mappage Product -> ProductViewModel utilisé pour aplatir les membres du produit
  • Définissez un mappage Category vers CategoryViewModel
  • Définissez un mappage ProductWithCategories -> ProductViewModel qui mappe les catégories, puis dans l'aftermap, mappez le Product:

    config.CreateMap<ProductWithCategories, ProductViewModel>() .ForMember(q => q.Id, option => option.Ignore()) // flattened in AfterMap .ForMember(q => q.Categories, option => option.MapFrom(q => q.Categories)) .AfterMap((src, dst) => Mapper.Map(src.Product, dst));

27
Georg Patscheider

À l'aide des versions récentes d'AutoMapper, vous pouvez effectuer les opérations suivantes:

config.CreateMap<Product, ProductViewModel>()
      .ForMember(q => q.Categories, option => option.Ignore());

config.CreateMap<ProductWithCategories, ProductViewModel>()
      .ConstructUsing(s => AutoMapper.Mapper.Map<ProductViewModel>(s.Product))
      .ForMember(q => q.Categories, option => option.MapFrom(q => q.Categories))
      .ForAllOtherMembers(o => o.Ignore();

ConstructUsing () est utilisé pour générer et remplir la classe de base à partir des enfants imbriqués [ren] de la source. Si vous avez plusieurs enfants imbriqués, vous devrez effectuer plusieurs appels de mappage pour mapper chacun d'eux successivement sur l'instance générée par le premier appel Map (). Le .ForAllOtherMembers () est relativement récent (si vous ne l'avez pas, obtenez une version plus récente d'AutoMapper.) Malheureusement, il est légèrement dangereux comme si vous ajoutez des membres de destination qui auront besoin de mappage mais oubliez de mettre à jour la carte, la validation de la configuration ne sera pas attrape ça.

13
Cliff Hudson

La ligne incriminée qui génère l'erreur est

.ForMember(q => q, option => option.MapFrom(q => q.Product))

Le message d'erreur est difficile à comprendre, mais cela signifie que vous devez indiquer explicitement les propriétés de destination:

.ForMember(q => q.Id, option => option.MapFrom(q => q.Product.Id))
.ForMember(q => q.OtherProperty, option => option.MapFrom(q => q.Product.OtherProperty))

Vous devez également définir un mappage de Category vers CategoryViewModel pour

.ForMember(q => q.Categories, option => option.MapFrom(q => q.Categories))

travailler:

config.CreateMap<Category, CategoryViewModel>();
0
Georg Patscheider

tu devrais faire comme -

AutoMapper.Mapper.CreateMap<Category, CategoryViewModel>();
AutoMapper.Mapper.CreateMap<ProductWithCategories, ProductViewModel>()
     .ForMember(a => a.Id, b => b.ResolveUsing(c => c.Product != null ? c.Product.MyProperty : 0))
     .ForMember(a => a.Categories, b => b.ResolveUsing(c => c.Categories));

Mais il est préférable d'envelopper ces propriétés de ProductViewModel (accessoires comme Id) dans une autre classe. Et créez une autre carte pour que les choses fonctionnent de manière automappeur.

0
Amit Kumar Ghosh