'Entity Framework Core child entities only show up when I debug through code
I'm having this really weird scenario where I have a component in MVC that will call a service to grab all the categories in the database and include its child categories, but for some reason two things happen:
- The child categories load fine on the index page, but not on any other page
- When I debug through code and open up the child categories in the debug section they work on all pages
Component code:
public class Navbar : ViewComponent
{
private readonly ICategoryService _categoryService;
public Navbar(ICategoryService categoryService)
{
_categoryService = categoryService;
}
public IViewComponentResult Invoke()
{
var categories = _categoryService.GetAllCategories().Where(c => c.ParentCategoryId == null && c.ShowInNavbar);
return View(categories.Select(NavbarItemViewModelMapper.Map));
}
}
Service code:
public class CategoryService : ICategoryService
{
private readonly ApplicationDbContext _context;
public CategoryService(ApplicationDbContext context)
{
_context = context;
}
public IEnumerable<Category> GetAllCategories()
{
return _context.Categories
.Include(c => c.ParentCategory)
.ThenInclude(c => c.ChildCategories)
.Include(c => c.Image)
.Include(c => c.Products);
}
public Category GetCategoryById(int id)
{
return _context.Categories.FirstOrDefault(c => c.Id == id);
}
}
Mapper code:
public static class NavbarItemViewModelMapper
{
public static NavbarItemViewModel Map(Category category) =>
new()
{
Name = category.Name,
Link = $"/produto/{category.Id}",
ParentNavbarItem = category.ParentCategory != null ? Map(category.ParentCategory) : null,
ChildNavbarItems = category.ChildCategories != null && category.ChildCategories.Any() ? category.ChildCategories.Select(c => Map(c)) : new List<NavbarItemViewModel>()
};
}
A few screenshots:
Debugging through category list before mapping
When I debug through the code like the last screenshot the child categories work just fine.
A workaround I found is to do this:
public IViewComponentResult Invoke()
{
var categories = _categoryService.GetAllCategories().Where(c => c.ParentCategoryId == null && c.ShowInNavbar);
foreach (var category in categories)
{
category.ChildCategories = category.ChildCategories?.Any() == true ? category.ChildCategories.ToList() : category.ChildCategories;
}
return View(categories.Select(NavbarItemViewModelMapper.Map));
}
But I still don't understand why it acts differently in different pages.
Solution 1:[1]
I would suggest to rewrite your code slightly to reduce number of enumerations which puts stress on your database and your recursive mapper might confuse EF Core change tracker (guesswork from my side, but this is probably the reason).
Choose one of the following
- If you need to filter server-side (in the actual db call), you should return
IQueryable<Category>notIEnumerable<Category>fromGetAllCategories(). I would rename the method toGetAllCategoriesQuery()to reduce confusion later on. Then compose on the query like you doGetAllCategoriesQuery().Where(c => c.ParentCategoryId == null && c.ShowInNavbar)but add.ToList()(or.ToArray()) in the final enumeration to ensure a single database call.
public IQueryable<Category> GetAllCategoriesQuery()
{
return _context.Categories
.Include(c => c.ParentCategory)
.ThenInclude(c => c.ChildCategories)
.Include(c => c.Image)
.Include(c => c.Products);
}
// ...
var categories = _categoryService.GetAllCategoriesQuery()
.Where(c => c.ParentCategoryId == null && c.ShowInNavbar)
.ToList(); // important
- Return all items from the database (if the query is not heavy) by returning
IEnumerable<Category>(orList<Category>) fromGetAllCategories()but then you definitely must add.ToArray()(or.ToList()) to ensure a single database call.
public IEnumerable<Category> GetAllCategories()
{
return _context.Categories
.Include(c => c.ParentCategory)
.ThenInclude(c => c.ChildCategories)
.Include(c => c.Image)
.Include(c => c.Products)
.ToList(); // important
}
Sidenote: You should consider to use async pattern, if possible.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | joakimriedel |
