Сохранение иерархического (древовидного) объекта итеративно с родительским и дочерним в C#

#c#

#c#

Вопрос:

У меня есть объект таблицы базы данных, который:

 [Table("TreeViewDb")]
public class TreeViewDb
{
    [Key]
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public int Id { get; set; }
    public int ParentId { get; set; }
    public string Name { get; set; }    
}
 

И у меня есть модель представления, которая :

 public class TreeView
{
    public TreeView()
    {
        Children = new List<TreeView>();
    }

    public int Id { get; set; }
    public int ParentId { get; set; }
    public string Name { get; set; }

    // children
    public List<TreeView> Children { get; set; }
}
 

Теперь мне нужно сохранить TreeView в базе данных. Во время сохранения детей или детей младше детей до n-го уровня. Но мой приведенный ниже метод подходит только для уровня 3. Как я могу перейти на n-й уровень, чтобы сохранить дочерние и родительские объекты рекурсивным способом?

 public bool SaveOrUpdateTreeView(TreeView viewModel)
{
    // Level 1
    var parentModel = new TreeViewDb
    {
        Id = viewModel.Id,
        ParentId = viewModel.ParentId,
        Name = viewModel.Name
    };

    // Save or update object and return primary key
    var parentId = _dataRepository.SaveOrUpdateTreeView(parentModel);

    // Level 2
    foreach (var child in viewModel.Children)
    {
        var childModel = new TreeViewDb
        {
            Id = viewModel.Id,
            ParentId = parentId, // Parent Primary Key
            Name = viewModel.Name
        };

        // Save or update object and return primary key
        var childId = _dataRepository.SaveOrUpdateTreeView(childModel);

        // Level 3
        foreach (var grandChild in child.Children)
        {
            var grandChildModel = new TreeViewDb
            {
                Id = viewModel.Id,
                ParentId = childId, // Child Primary Key
                Name = viewModel.Name
            };

            _dataRepository.SaveOrUpdateTreeView(grandChildModel);
        }
    }

    return true;
}
 

Ответ №1:

Вот ты где:

  • используйте Stack , включая глубину элемента
  • перебирайте узлы по порядку
  • решайте сохранять его или нет

введите описание изображения здесь

Код:

 using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;

namespace zzzzzzzz
{
    internal static class Program
    {
        private static void Main(string[] args)
        {
            var node = new Node("node 0 @ level 0")
            {
                new Node("node 1 @ level 1")
                {
                    new Node("node 2 @ level 2")
                    {
                        new Node("node 3 @ level 3")
                        {
                            new Node("node 4 @ level 4")
                        }
                    }
                },
                new Node("node 5 @ level 1")
                {
                    new Node("node 6 @ level 2")
                    {
                        new Node("node 7 @ level 3")
                    }
                }
            };


            var stack = new Stack<(Node, int)>();

            stack.Push((node, 0));

            while (stack.Any())
            {
                var (current, depth) = stack.Pop();

                ProcessNode(current, depth);

                foreach (var item in current.Reverse())
                {
                    stack.Push((item, depth   1));
                }
            }
        }

        private static void ProcessNode(Node node, int depth)
        {
            Console.Write($"{new string('t', depth)}{node}");
            var color = Console.ForegroundColor;
            Console.ForegroundColor = depth < 3 ? ConsoleColor.Green : ConsoleColor.Red;
            Console.Write($" {(depth < 3 ? "SAVED" : "IGNORED")}{Environment.NewLine}");
            Console.ForegroundColor = color;
        }
    }

    public class Node : IList<Node>
    {
        public Node(string text)
        {
            Text = text;
        }

        public string Text { get; set; }

        private IList<Node> List { get; } = new List<Node>();

        public IEnumerator<Node> GetEnumerator()
        {
            return List.GetEnumerator();
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            return ((IEnumerable) List).GetEnumerator();
        }

        public void Add(Node item)
        {
            List.Add(item);
        }

        public void Clear()
        {
            List.Clear();
        }

        public bool Contains(Node item)
        {
            return List.Contains(item);
        }

        public void CopyTo(Node[] array, int arrayIndex)
        {
            List.CopyTo(array, arrayIndex);
        }

        public bool Remove(Node item)
        {
            return List.Remove(item);
        }

        public int Count => List.Count;

        public bool IsReadOnly => List.IsReadOnly;

        public int IndexOf(Node item)
        {
            return List.IndexOf(item);
        }

        public void Insert(int index, Node item)
        {
            List.Insert(index, item);
        }

        public void RemoveAt(int index)
        {
            List.RemoveAt(index);
        }

        public Node this[int index]
        {
            get => List[index];
            set => List[index] = value;
        }

        public override string ToString()
        {
            return $"{nameof(Text)}: {Text}";
        }
    }
}
 

Комментарии:

1. В настоящее время мой метод поддерживает сохранение трехуровневых данных. Но я хочу реорганизовать свою функцию для поддержки сохранения n-уровня данных. Так что мне не нужна глубина.

Ответ №2:

Вы можете создать универсальный итератор, который позволит вам перебирать все узлы в дереве:

     public static IEnumerable<(T Parent, T Node, int Level)> BreadthFirst<T>(T self, Func<T, IEnumerable<T>> selector)
    {
        var queue = new Queue<(T Parent, T Node, int Level)>();
        queue.Enqueue((self, self, 0));
        while (queue.Count > 0)
        {
            var current = queue.Dequeue();
            yield return current;
            var node = current.Node;
            var level = current.Level   1;
            foreach (var child in selector(node))
            {
                queue.Enqueue((node, child, level));
            }
        }
    }
 

Вызывается как

 var nodes = BreadthFirst(viewModel, v => v.Children).Where(t => t.Level < 3);
 

Корневой узел будет иметь самого себя в качестве родительского, поэтому вам нужно будет проверить это, если вы хотите обработать его каким-то другим способом.