I am working on mapping a few database entities for a reporting tool.
At the moment, there are a few computed properties depending on navigation properties for their loading. They've been bound through AutoMapper to ease the process.
public class Customer
{
public long Id { get; set; }
public string Name { get; set; }
public virtual ICollection<Foo> Foos { get; set; }
public virtual ICollection<Bar> Bars { get; set; }
}
public class CustomerDto
{
public long Id { get; set; }
public string Name { get; set; }
public long TotalNumberOfFoos { get; set; }
public long NumberOfBarsWithCondition { get; set; }
}
public class CustomerProfile : Profile
{
public CustomerProfile()
{
CreateMap<Customer, CustomerDto>()
.ForMember(d => d.TotalNumberOfFoos, p => p.MapFrom(c => c.Foos.Count))
.ForMember(d => d.NumberOfBarsWithCondition, p => p.MapFrom(c => c.Bars.Where(b => b.BarProperty == "something").Count()));
}
}
public class CustomerController : Controller
{
public async Task<List<CustomerDto>> CustomersByName(string name)
{
using (var db = new MyDbContext())
{
return await db.Customers
.ProjectTo<CustomerDto>(_mapper.ConfigurationProvider)
.Where(c => c.Name == name).ToListAsync();
}
}
}
Of course, the queries to retrieve these properties can become quite expensive as the size of the database increases, and they're not always needed in the final report.
The idea is to have an option for the user to choose if they want them included or not in the final report, but I haven't found a way to make the mapping optional at query time.
Is there a way to do this automatically, or am I forced to materialize the list and query these properties myself separately, losing the advantage of having computed properties from the database?
CodePudding user response:
What you need is to utilize the so called AutoMapper Explicit expansion feature. Which should probably be called "explicit property inclusion" (not to be mixed with EF Core Include
which is only for navigations), because it works for any destination property, and what it does it to rather include it automatically in the generated projection (Select
), include it only when you opt-in explicitly.
So, you need first to configure such properties as ExplicitExpansion()
, e.g.
CreateMap<Customer, CustomerDto>()
.ForMember(d => d.TotalNumberOfFoos, p =>
{
p.MapFrom(c => c.Foos.Count);
p.ExplicitExpansion();
})
.ForMember(d => d.NumberOfBarsWithCondition, p =>
{
p.MapFrom(c => c.Bars.Where(b => b.BarProperty == "something").Count());
p.ExplicitExpansion();
});
Now by default they won't be populated. Use the additional arguments of ProjectTo
to pass which ones you want to "expand" (include), e.g.
.ProjectTo<CustomerDto>(_mapper.ConfigurationProvider, e => e.TotalNumberOfFoos)