1
votes

Is it possible to convert a List to Tree so that when I delete parent node all child nodes will be deleted automatically?

Reason for this is that I have recursive categories and I want to show all categories except child categories (recursively) and easiest way in my mind is to do something like in the picture.

As you can see in this scenario when I delete red node all green nodes will remove themselves automatically. What are my options?

Tree

Here is the model I'm using if it makes any difference.

public class Category
{
    public int Id { get; set; }

    public int? RootCategoryId { get; set; }
    public virtual Category RootCategory { get; set; }
    public virtual ICollection<Category> ChildCategories { get; set; }
}

This is what I have so far

// This would eliminate only current category but not its children = bad
var availableCategories = _db.Categories.Where(x => x.Id != currentlyEditedId);

So for example when I want to edit a category I would call _db.Categories.Where(x => x.Id != currentlyEditedId);. This would eliminate the current category (in the picture it would delete red one) however the green ones would stay.

How can I make sure that if I delete the red one the green ones will delete as well?

In the end I want to have a list with all black items from the picture.

2
What problems do you have using the Category class you've just shown?Servy
@Servy Problem is he gets recursive tree of categories, that's too much data, he wants to control depth of recursion.skmasq
@skmasq He never said that. There is no indication of the quantity of data, or that the depth is too much to manage.Servy
@Servy I updated my question, maybe it is more clear now?Stan
why can't you just create a DeleteChildren(Category c) functionJonesopolis

2 Answers

1
votes

To delete an item you'll need to delete each of them individually; that's how the db context works. You can get all of the items to delete simply enough though. Here is the simple algorithm to traverse a tree:

public static IEnumerable<Category> Traverse(Category root)
{
    var stack = new Stack<Category>();

    stack.Push(root);

    while (stack.Any())
    {
        var next = stack.Pop();
        yield return next;
        foreach (var child in next.ChildCategories)
            stack.Push(child);
    }
}

Now you can do something like:

public static void DeleteCategory(Category category)
{
    var items = Traverse(category).ToList();
    var itemsToDelete = _db.Categories.Where(cat => items.Contains(cat));
    //delete items
}

If you instead just want to remove an item from it's in-memory collection instead you only need to call Remove on it's parent. If you don't have the parent, and only have the node to remove and the root, then you'll need to have another method to traverse the tree:

public static bool Remove(Category root, int id)
{
    var stack = new Stack<Category>();

    stack.Push(root);

    while (stack.Any())
    {
        var next = stack.Pop();
        foreach (var child in next.ChildCategories)
        {
            if (child.Id == id)
            {
                next.ChildCategories.Remove(child);
                return true;
            }
            stack.Push(child);
        }
    }

    return false;
}
1
votes

This is what I wanted. Pass in category list and remove the current one and recursively remove all its children.

/// <summary>
/// Get list of all categories except current one as well as all it's child categories
/// </summary>
/// <param name="id">Current category id</param>
/// <param name="categories">List of categories</param>
/// <returns>List of categories</returns>
public static List<Category> CategoriesWithoutChildren(int id, List<Category> categories)
{
    var currentCategory = categories.Single(x => x.Id == id);
    categories.Remove(currentCategory);

    if (currentCategory.ChildCategories.Count > 0)
    {
        currentCategory.ChildCategories.ToList().ForEach(x =>
        {
            categories = CategoriesWithoutChildren(x.Id, categories);
        });
    }

    return categories;
}