Projecting self referencing multi level entities in Entity Framework 6.
Let\'s say that I have a Category
entity as follows:
public clas
It might not be elegant, but a suitable solution is to have in your code a shared IDictionary<int, CategoryView>
. When you are going to map an entity Category
into a CategoryView
check first if you have already created this object and set the reference stored in the dictionary instead of creating a CategoryView
instance. When creating a new instance, store it in the dictionary. This is a way to take advantage of the primary key of your entity to avoid the infinite recursion issue in your code.
Also, notice that in your CategoryView
object you shouldn't be referencing Category
instances. Update it to reference CategoryView
instances like this.
public class CategoryView
{
public int Id { get; set; }
public int? ParentCategoryId { get; set; }
// other properties ...
public CategoryView ParentCategory { get; set; }
public List<CategoryView> SubCategories { get; set; }
public int ProductCount { get; set; }
public CategoryView()
{
SubCategories = new List<CategoryView>();
}
}
I can't say if it's the best or elegant way, but it's pretty standard and efficient non recursive way of building such structure.
Start with loading all categories without parent / child object links using a simple projection:
var allCategories = db.Categories
.Select(c => new CategoryView
{
Id = c.CategoryId,
ParentCategoryId = c.ParentCategoryId,
Name = c.Name,
Description = c.Description,
ProductCount = c.Products.Count()
})
.ToList();
then create a fast lookup data structure for finding CategoryView
by Id
:
var categoryById = allCategories.ToDictionary(c => c.Id);
then link the subcategories to their parents using the previously prepared data structures:
foreach (var category in allCategories.Where(c => c.ParentCategoryId != null))
{
category.ParentCategory = categoryById[category.ParentCategoryId.Value];
category.ParentCategory.SubCategories.Add(category);
}
At this point, the tree links are ready. Depending of your needs. either return the allCategories
or the root categories if you need a real tree representation:
return allCategories.Where(c => c.ParentCategoryId == null);
P.S. Actually the allCategories
list can be avoided, since categoryById.Values
could serve the same purpose.