2

We have something like this

var categories = _context.Categories.Include("Categories1.Categories1.Categories1");

That works and handles sub-categories up to 4-level deep (which is enough for now but who knows the future)

Is there a better way to do it?

More info

We use database-first. Category table has these columns:

  • Id
  • ParentCategoryId <-- this has foreign key to Category.Id
Aximili
  • 28,626
  • 56
  • 157
  • 216
  • No such capability exists out of the box so far. – Ivan Stoev Jul 18 '18 at 08:59
  • But in case you don't need `IQueryable` result, https://stackoverflow.com/questions/46160780/map-category-parent-id-self-referencing-table-structure-to-ef-core-entity can give you an idea how you can do that in memory with single db query and even w/o `Include` (but with the cost of loading the whole table in memory). – Ivan Stoev Jul 22 '18 at 17:16

2 Answers2

1

In this particular case I think a recursive property might be a good bet. Trying to do this from memory (been years), performance won't be great. NO lazy loading and NO explicit loading.

public class Category {
    public int Id {get; set;}
    // other stuff
    public List<Category> MyChildren {
        get { return _context.Categories.Where(x => x.ParentCategoryId == Id).ToList<Category>(); }
    } 
}

This should give you a hierarchical graph starting with a given node.

var justOne = _context.Categories.FirstOrDefault(x => x.Id = <myval>);

Downside is you will have to parse/use the result recursively and potentially it grows exponentially.

Clarification: The use of _context in the recursion is not allowed by EF and was used for illustration purposes. In the repository/UoW or business layer you could use the same technique to "assemble" the finished entity by having a property of a method that calls the method recursively.

Just for fun, here's the same recursion technique (but not as a property, don't have time right now).

public class Category       // EF entity
{
    public int Id { get; set; }
    public int ParentId { get; set; }
    public virtual List<Category> MyChildren { get; set; }
}
public static class MVVMCategory
{
    public static Category GetCategory(int id)
    {
        Category result = _context.Categories.FirstOrDefault(x => x.Id == id);

        result.MyChildren = GetChildren(id);

        return result;
    }

    public static List<Category> GetChildren(int id)
    {
        List<Category> result = _context.Categories.Where(x => x.ParentId == id).ToList<Category>();
        foreach (var item in result)
        {
            item.MyChildren = GetChildren(item.Id);
        }
        return result;
    }
}

One thing to notice. I added virtual to the list for lazy loading because I am loading children "by hand" so to speak.

John White
  • 705
  • 4
  • 12
1

Firstly, add data annotations and make properties readable

public partial class Category
{

    public Category()
    {
        this.Children = new HashSet<Category>();
    }

    [Key]
    public int Id { get; set; }

    public string WhatEverProperties { get; set; }

    public int ParentCategoryId { get; set; }


    [ForeignKey("ParentCategoryId")]
    [InverseProperty("Category")]
    public Category Parent { get; set; } // name "Category1" as "Parent"

    [InverseProperty("Category")]
    public ICollection<Category> Children { get; set; } // Name it as Children
}

then, let's say we have got a category,

var category = context.Categories
    .Include(x => x.Parent)
    .Include(x => x.Children)
    .FirstOrDefault(filter);

then we get its parents with:

var rootCategory = category.Parent?.Parent?.Parent; //up-to 4 levels by your request

by following extension, we can easily get its level:

///this extension works only if you used `.Include(x => x.Parent)` from query
public static class CategoryExtensions
{
    public static int Level(this Category category)
    {
        if (category.Parent == null)
        {
            return 0;
        }

        return category.Parent.Level() + 1;
    }
}
Dongdong
  • 2,208
  • 19
  • 28