2
votes

I've got a problem doing third level mapping. The classes are below:

public class Company {
    public Contact MainContact { get; private set; }
}


public class Contact {
    public PersonsName ContactName { get; private set; }
}

public  class PersonsName  {
    public String FirstName { get; private set; }
    public String LastName { get; private set; }

}

public class CreateCompanyViewModel {
    [Required]
    [MaxLength(25, ErrorMessage = "Only 25 characters are allowed.")]
    [Display(Name = "First Name")]
    public String FirstName { get; set; }

    [Required]
    [MaxLength(25, ErrorMessage = "Only 25 characters are allowed.")]
    [Display(Name = "Last Name")]
    public String LastName { get; set; }
}

I've tried the following with no success:

Mapper.CreateMap<CreateCompanyViewModel, PersonsName>()
    .ForMember(dest => dest.FirstName, opt => opt.MapFrom(src => src.FirstName))
    .ForMember(dest => dest.LastName, opt => opt.MapFrom(src => src.LastName));

Mapper.CreateMap<Contact, PersonsName>()
    .ForMember(dest => dest.FirstName, opt => opt.MapFrom(src => src.ContactName.FirstName))
    .ForMember(dest => dest.LastName, opt => opt.MapFrom(src => src.ContactName.LastName));

Mapper.CreateMap<CreateCompanyViewModel, Company>()
    .ForMember(dest => dest.MainContact.ContactName.FirstName, opt => opt.MapFrom(src => src.FirstName))
    .ForMember(dest => dest.MainContact.ContactName.FirstName, opt => opt.MapFrom(src => src.LastName));

The error message I get is:

Expression 'dest => dest.MainContact.ContactName.FirstName' must resolve to top-level member and not any child object's properties. Use a custom resolver on the child type or the AfterMap option instead. Parameter name: lambdaExpression

1
Title on that is misleading, it actually is a question about mapping to multiple classes, all at the top level. - Eric Yeoman
Can you show CreateCompanyViewModel? - Andrew Whitaker
Added CreateCompanyViewModel, somehow left that out oops. - Eric Yeoman

1 Answers

2
votes

You can use the following configuration:

/* Innermost mapping */
Mapper.CreateMap<CreateCompanyViewModel, PersonsName>();
// You don't need the individual member mappings since the names match.

/* Second-level */
Mapper.CreateMap<CreateCompanyViewModel, Contact>()
    .ForMember(dest => dest.ContactName, opt => opt.MapFrom(src => src));

/* Top level */
Mapper.CreateMap<CreateCompanyViewModel, Company>()
    .ForMember(dest => dest.MainContact, opt => opt.MapFrom(src => src));

The idea is that you use the source type (CreateCompanyViewModel) as the source for the nested types as well. You accomplish this using the MapFrom(src => src) call inside of ForMember.

Example: https://dotnetfiddle.net/XBJMba

Since the src => src bit looks a little odd, you could create an extension method that makes what you're doing more straightforward:

public static class AutoMapperExtensions
{
    public static void MapFromSelf<TSourceType>(
        this IMemberConfigurationExpression<TSourceType> expression)
    {
        expression.MapFrom(s => s);
    }
}

Usage:

Mapper.CreateMap<CreateCompanyViewModel, Contact>()
    .ForMember(dest => dest.ContactName, opt => opt.MapFromSelf());

Mapper.CreateMap<CreateCompanyViewModel, Company>()
    .ForMember(dest => dest.MainContact, opt => opt.MapFromSelf());