How to use AutoMapper to display a destination object with a child in the source object?

I have source and target objects like this:

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

So, I need to map the values โ€‹โ€‹of source.Product to dest , and then source.Categories to dest.Categories . Is this possible with AutoMapper?

I tried this and I was not surprised when it failed:

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

Here is the exception I received:

[AutoMapperConfigurationException: Customization for members is only supported for individual top-level members by type.]

+6
source share
4 answers

After some discussion with the OP, it turns out that his main need is to quickly map the child / nested object inside the source object to a flattened destination. He does not want to write a mapping for each recipient property.

Here's how to do it:

  • Define a Product โ†’ ProductViewModel used to align product items
  • Define Category Display - CategoryViewModel
  • Define a ProductWithCategories โ†’ ProductViewModel display that displays categories, and then in the aftermap display 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));

+11
source

Using the latest versions of AutoMapper, you can do something like the following:

 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 () is used to generate and populate the base class from a nested child [ren] source. If you have more than one such nested child, you need to make several display calls in order to map each of them to the instance generated by the first call to Map () .. ForAllOtherMembers () relatively recently (if you do not have one, get a newer version AutoMapper.) Unfortunately, it is a little unsafe, as if you add target elements that will need to be mapped, but forget to update the map, the configuration check will not catch it.

+2
source

The line of violation that generates the error is

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

The error message is hard to understand, but that means you must explicitly specify the destination properties:

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

You must also define a mapping from Category to CategoryViewModel for

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

for work:

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

you need to do -

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

But it's better to wrap these properties from ProductViewModel (props of type Id ) inside another class. And create another card so that everything can work automatically.

0
source

All Articles