I have reviewed quite a few SO articles related to this question and have used a few of those questions/answers to get close to an answer for my situation, but I can't quite get it right.
I have an EF6 context with 5 tables (Grandparent, GrandparentParent, Parent, ParentChild, and Child). The GP and PC tables are simple many-to-many relationships that tie the family hierarchy together.
My business requirement is to query database and return a list of grandparent objects that contain a list of all their grandchildren without having the grandchildren nested under a parent. I have 3 ViewModel classes that are subsets of their corresponding tables and I want to flatten the database hierarchy down to a list of GrandparentViewModel classes where GVM =
public class GrandparentViewModel
{
public int GrandparentId { get; set; }
public string Name { get; set; }
public List<ChildViewModel> Grandchildren { get; set; }
}
It is also important to note that I am using AutoMapper's ProjectTo<> extension method in the query so I can offload the projection and only select a subset of fields from each table....ie...I have DateOfBirth on each table and I do not want to query and/or return that field.
In my attempt to get this working, I stumbled onto AutoMapper's ITypeConverter interface and built a class that implements that interface for the grandparent and for the child. Here are those classes:
public class GrandparentConverter : ITypeConverter<Grandparent, GrandparentViewModel>
{
public GrandparentViewModel Convert(ResolutionContext context)
{
var entities = context.SourceValue as Grandparent;
return entities
.Select(x => new GrandparentViewModel
{
GrandparentId = x.GrandparentId,
Name = x.Name,
Grandchildren = AutoMapper.Mapper.Map<IEnumerable<Parent>, List<ChildViewModel>>(x.Parents)
}).ToList();
//return new GrandparentViewModel
//{
// GrandparentId = entities.GrandparentId,
// Name = entities.Name,
// Grandchildren = entities.Parents.SelectMany(x => x.Children.Select(y => new ChildViewModel
// {
// ChildId = y.ChildId,
// Name = y.Name
// }).ToList()).ToList()
//};
}
}
public class ChildConverter : ITypeConverter<IEnumerable<Parent>, List<ChildViewModel>>
{
public List<ChildViewModel> Convert(ResolutionContext context)
{
var entities = context.SourceValue as IEnumerable<Parent>;
return entities
.SelectMany(x => x.Children)
.Select(x => new ChildViewModel
{
ChildId = x.ChildId,
Name = x.Name
}).ToList();
}
}
Here is the consuming code:
MapperConfiguration mapper = new MapperConfiguration(cfg =>
{
cfg.CreateMap<Child, ChildViewModel>();
cfg.CreateMap<IEnumerable<Parent>, List<ChildViewModel>>().ConvertUsing<ChildConverter>();
cfg.CreateMap<Grandparent, GrandparentViewModel>();
cfg.CreateMap<IEnumerable<Grandparent>, List<GrandparentViewModel>>().ConvertUsing<GrandparentConverter>();
});
FamilyEntities db = new FamilyEntities();
List<GrandparentViewModel> entities = db.Set<Grandparent>()
.Where(x => x.GrandparentId == 1)
.ProjectTo<GrandparentViewModel>(mapper)
.ToList();
Currently, the solution builds, runs, and returns a singular GrandparentViewModel class as I would expect, but all of the fields (including those on grandparent...ie...name) are null.
Any ideas on what I am missing and/or why the data is not being populated? I tried setting a breakpoint in that TypeConverter class, but it never executes even though the only mapping configuration specified in AutoMapper's MapperConfiguration class is using that converter. If I remove that CreateMap call, AutoMapper then throws its "Missing configuration" error.
Any advice is greatly appreciated.
EDIT: I built a scaled down version of this issue and when I don't build the AutoMapper MapperConfiguration and pass it to the ProjectTo method, the breakpoint in the custom TypeConverter class is hit. Therefore, it appears that in my original post that the issue is that AutoMapper isn't actually using the converter that is specified in the ConvertUsing method.
Confirmed this is a bug in AutoMapper's ProjectTo logic. Issue 1216 has been logged with AutoMapper to correct this issue. In the meantime, I was given a workaround that seems to work.
cfg.CreateMap<Grandparent, GrandparentViewModel>()
.ForMember(d => d.Grandchildren,
opt.MapFrom(s => s.Parents.SelectMany(x => x.Children));