Search code examples
entity-frameworklinqautomapper

How to use Automapper to map navigation property viewmodels as well


I am trying to use Automapper to return a navigation property ViewModel

Models:

public class Month

{
    public int Id { get; set; }
    public string MonthName { get; set; }
    public int YearId { get; set; }
    public Year Year { get; set; }
}

public class Year

{
    public int Id { get; set; }
    public string YearName { get; set; }
}

ViewModels:

public class MonthViewModel

{
    public int Id { get; set; }
    public string MonthName { get; set; }
    Display(Name = "Financial Year")
    public int YearId { get; set; }
    public YearViewModel Year { get; set; }
}

public class YearViewModel

{
    public int Id { get; set; }
    Display(Name = "Financial Year Name")
    public string YearName { get; set; }
}

I am currently have a ASP.NET MVC view Mapping displaying a list of Months in a table and I need to inlcude the Year Name as a column

public  IActionResult Index()
{
    var months =  _monthRepo.GetAll().Include(m=>m.Year);

    IEnumerable<MonthViewModel> monthsVms = MonthViewModel.MapToViewModel(months);

    return View(monthsVms);
}



public static IEnumerable<MonthViewModel> MapToViewModel(IEnumerable<Month> dm)
{
    Mapper.CreateMap<Month, MonthViewModel>();
    Mapper.CreateMap<Year, YearViewModel>();
    return Mapper.Map<IEnumerable<Month>, IEnumerable<MonthViewModel>>(dm);
}

//However in my view when I want to refer to the year name like below:


@foreach (var item in Model)
{

    @Html.EditorFor(modelItem => item.YearViewModel.YearName)

}

However item.YearViewModel is null.

Question:

  1. How do I setup AutoMapper to map the related navigation property ViewModel's Model to its ViewModel and include it as a navigation property to my ViewModel?

There is a 1 to 0/1 relation between the main class and its navigation property. e.g. I want to display a list of Months with Columns Month Name and Year Name next to each other. (because I want to only use ViewModels Data Annotations)

I found a similar question here but I couldn't get it to work

 Mapper.CreateMap<Month, MonthViewModel>().ForMember(dest => dest.YearViewModel.YearName, opt => opt.MapFrom(src => src.Year.YearName));

 Mapper.CreateMap<Year, YearViewModel>();

 return Mapper.Map<IEnumerable<Month>, IEnumerable<MonthViewModel>>(dm);

but I get

ArgumentException: Expression 'dest => dest.FinancialYearViewModel.FinancialYearName' 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

I get the same problem when I try

 Mapper.CreateMap<Year, YearViewModel>();
 AutoMapper.Mapper.CreateMap<Month, MonthViewModel>()
     .ForMember(a => a.YearViewModel.YearName, b => b.ResolveUsing(c => c.Year.YearName));

Solution provide in answer was to map the whole navigation property:

Mapper.CreateMap<Year, YearViewModel>();
AutoMapper.Mapper.CreateMap<Month, MonthViewModel>()
     .ForMember(a => a.YearViewModel, b => b.ResolveUsing(c => c.Year));

Solution

  • Possibly this is what you want -

    AutoMapper.Mapper.CreateMap<Year, YearViewModel>();
    AutoMapper.Mapper.CreateMap<Month, MonthViewModel>()
                .ForMember(a => a.Year, b => b.ResolveUsing(c => c.Year));
    var o = AutoMapper.Mapper.Map<MonthViewModel>(new Month
                {
                    Id = 1,
                    MonthName = "January",
                    YearId = DateTime.Now.Year,
                    Year = new Year
                    {
                        Id = DateTime.Now.Year,
                        YearName = DateTime.Now.Year.ToString()
                    }
                }); // should map year as well.