17

I have the source and destination 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, my need is to map the values of source.Product into dest, and then source.Categories into dest.Categories. Is it possible with AutoMapper?

I have 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: Custom configuration for members is only supported for top-level individual members on a type.]

Cœur
  • 37,241
  • 25
  • 195
  • 267
Luke Vo
  • 17,859
  • 21
  • 105
  • 181

4 Answers4

33

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

Here is a way to achieve this:

  • Define a mapping Product -> ProductViewModel used to flatten the members of Product
  • Define a mapping Category to CategoryViewModel
  • Define a mapping ProductWithCategories -> ProductViewModel that maps the categories, and then in the aftermap, map the 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));

Georg Patscheider
  • 9,357
  • 1
  • 26
  • 36
17

Using recent 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 the nested child[ren] of the source. If you have more than one such nested child, you would need to make several mapping calls to map each of them successively onto the instance generated by the first Map() call. The .ForAllOtherMembers() is relatively recent (if you don't have it, get a newer version of AutoMapper.) Unfortunately it's slightly unsafe as if you add destination members which will need mapping but forget to update the map, configuration validation will not catch it.

Cliff Hudson
  • 311
  • 2
  • 6
  • 17
    Just for people using instance-based initialization, change to `.ConstructUsing((s, ctx) => ctx.Mapper.Map(s.Product))`. You can access the runtime mapper on the ResolutionContext. – kamranicus Mar 01 '17 at 18:59
1

The offending line that generates the error is

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

The error message is hard to understand, but it means you have to state the destination properties explicitly:

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

You also have to define a mapping from Category to CategoryViewModel for

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

to work:

config.CreateMap<Category, CategoryViewModel>();
Georg Patscheider
  • 9,357
  • 1
  • 26
  • 36
  • 1
    I don't know why the editor changed my question title. I think my original title is more informative. I understand why I get the error, my main need is to quickly map a child/nested object inside the source object to my flattened destination object. It's not nice if I have to specify each property of `Product`, it kill AutoMapper's purpose. – Luke Vo Feb 23 '16 at 09:12
  • Wraping the data of Product instead of having them flat in the ViewModel would make that easier to write. Or you could define a Mapping between `Product` and `ProductViewModel`and map the Categories in `AfterMap`. – Georg Patscheider Feb 23 '16 at 09:16
0

you should do like -

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 is better to wrap those properties from ProductViewModel (props like Id) inside another class. And create another map for things to work automapper way.

Amit Kumar Ghosh
  • 3,618
  • 1
  • 20
  • 24
  • 1
    Yes, I have thinked about that before, but I post this question here to see if there is a way to accomplish it without making nested class in the destination class as well. – Luke Vo Feb 23 '16 at 09:13
  • you are free to create your own implementations for `TypeConverter` or `ValueResolver`. They allow you to actually control the creation of your destination object. – Amit Kumar Ghosh Feb 23 '16 at 09:15