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.]
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:
Product
-> ProductViewModel
utilisé pour aplatir les membres du produitCategory
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));
À 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.
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>();
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.