{"id":413547,"date":"2024-06-29T23:11:44","date_gmt":"2024-06-29T23:11:44","guid":{"rendered":"http:\/\/savepearlharbor.com\/?p=413547"},"modified":"-0001-11-30T00:00:00","modified_gmt":"-0001-11-29T21:00:00","slug":"","status":"publish","type":"post","link":"https:\/\/savepearlharbor.com\/?p=413547","title":{"rendered":"<span>Tree Structure in EF Core: How to configure a self-referencing table and use it<\/span>"},"content":{"rendered":"<div><!--[--><!--]--><\/div>\n<div id=\"post-content-body\">\n<div>\n<div class=\"article-formatted-body article-formatted-body article-formatted-body_version-1\">\n<div xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\">\n<p>One of the very common questions I am getting from <code>.NET<\/code> community is how to configure and use the tree structures in <code>EF Core<\/code>. This story is one of the possible ways to do it.<\/p>\n<p>  <\/p>\n<p>The common tree structures are file tree, categories hierarchy, and so on. Let it be folders tree for example. The entity class will be a <code>Folder<\/code>:<\/p>\n<p>  <\/p>\n<pre><code class=\"cs\">public class Folder {     public Guid Id { get; set; }     public string Name { get; set; }           public Folder Parent { get; set; }     public Guid? ParentId { get; set; }     public ICollection&lt;Folder> SubFolders { get; } = new List&lt;Folder>(); }<\/code><\/pre>\n<p><a name=\"habracut\"><\/a>  <\/p>\n<p>This is how to configure DB schema via overriding <code>OnModelCreating<\/code> method of your <code>DbContext<\/code> class. This could be done via configuration property attributes on our entity class, but I prefer to define DB schema this way.<\/p>\n<p>  <\/p>\n<pre><code class=\"cs\">protected override void OnModelCreating(ModelBuilder modelBuilder) {     modelBuilder.Entity&lt;Folder>(entity =>     {         entity.HasKey(x => x.Id);         entity.Property(x=> x.Name);         entity.HasOne(x=> x.Parent)             .WithMany(x=> x.SubFolders)             .HasForeignKey(x=> x.ParentId)             .IsRequired(false)             .OnDelete(DeleteBehavior.Restrict);     });     \/\/ ... }<\/code><\/pre>\n<p>  <\/p>\n<p>This is how to load data from DB as a tree of folders, flatten it as a plain list of folder \u201cnodes\u201d, and get some details related to tree structure like node level, node parents, etc.:<\/p>\n<p>  <\/p>\n<pre><code class=\"cs\">{     List&lt;Folder> all = _dbContext.Folders.Include(x => x.Parent).ToList();     TreeExtensions.ITree&lt;Folder> virtualRootNode = all.ToTree((parent, child) => child.ParentId == parent.Id);     List&lt;TreeExtensions.ITree&lt;Folder>> rootLevelFoldersWithSubTree = virtualRootNode.Children.ToList();     List&lt;TreeExtensions.ITree&lt;Folder>> flattenedListOfFolderNodes = virtualRootNode.Children.Flatten(node => node.Children).ToList();     \/\/ Each Folder entity can be retrieved via node.Data property:     TreeExtensions.ITree&lt;Folder> folderNode = flattenedListOfFolderNodes.First(node => node.Data.Name == \"MyFolder\");     Folder folder = folderNode.Data;     int level = folderNode.Level;     bool isLeaf = folderNode.IsLeaf;     bool isRoot = folderNode.IsRoot;     ICollection&lt;TreeExtensions.ITree&lt;Folder>> children = folderNode.Children;     TreeExtensions.ITree&lt;Folder> parent = folderNode.Parent;     List&lt;Folder> parents = GetParents(folderNode); }<\/code><\/pre>\n<p>  <\/p>\n<p>This method demonstrates how to get all parents from the tree for specific node:<\/p>\n<p>  <\/p>\n<pre><code class=\"cs\">private static List&lt;T> GetParents&lt;T>(TreeExtensions.ITree&lt;T> node, List&lt;T> parentNodes = null) where T : class {     while (true)     {         parentNodes ??= new List&lt;T>();         if (node?.Parent?.Data == null) return parentNodes;         parentNodes.Add(node.Parent.Data);         node = node.Parent;     } }<\/code><\/pre>\n<p>  <\/p>\n<p>Tree operation extension methods below and helper interface for wrapping any entity into the tree node object. So technically your entity can be any class where you have relation <code>parent->children<\/code> (to get the plain list of nodes from the tree using <code>Flatten<\/code>) and <code>child-> parent<\/code> (to build the tree from the list using <code>ToTree<\/code> method):<\/p>\n<p>  <\/p>\n<pre><code class=\"cs\">public static class TreeExtensions {     \/\/\/ &lt;summary> Generic interface for tree node structure &lt;\/summary>     \/\/\/ &lt;typeparam name=\"T\">&lt;\/typeparam>     public interface ITree&lt;T>     {         T Data { get; }         ITree&lt;T> Parent { get; }         ICollection&lt;ITree&lt;T>> Children { get; }         bool IsRoot { get; }         bool IsLeaf { get; }         int Level { get; }     }     \/\/\/ &lt;summary> Flatten tree to plain list of nodes &lt;\/summary>     public static IEnumerable&lt;TNode> Flatten&lt;TNode>(this IEnumerable&lt;TNode> nodes, Func&lt;TNode, IEnumerable&lt;TNode>> childrenSelector)     {         if (nodes == null) throw new ArgumentNullException(nameof(nodes));         return nodes.SelectMany(c => childrenSelector(c).Flatten(childrenSelector)).Concat(nodes);     }     \/\/\/ &lt;summary> Converts given list to tree. &lt;\/summary>     \/\/\/ &lt;typeparam name=\"T\">Custom data type to associate with tree node.&lt;\/typeparam>     \/\/\/ &lt;param name=\"items\">The collection items.&lt;\/param>     \/\/\/ &lt;param name=\"parentSelector\">Expression to select parent.&lt;\/param>     public static ITree&lt;T> ToTree&lt;T>(this IList&lt;T> items, Func&lt;T, T, bool> parentSelector)     {         if (items == null) throw new ArgumentNullException(nameof(items));         var lookup = items.ToLookup(item => items.FirstOrDefault(parent => parentSelector(parent, item)),             child => child);         return Tree&lt;T>.FromLookup(lookup);     }     \/\/\/ &lt;summary> Internal implementation of &lt;see cref=\"ITree{T}\" \/>&lt;\/summary>     \/\/\/ &lt;typeparam name=\"T\">Custom data type to associate with tree node.&lt;\/typeparam>     internal class Tree&lt;T> : ITree&lt;T>     {         public T Data { get; }         public ITree&lt;T> Parent { get; private set; }         public ICollection&lt;ITree&lt;T>> Children { get; }         public bool IsRoot => Parent == null;         public bool IsLeaf => Children.Count == 0;         public int Level => IsRoot ? 0 : Parent.Level + 1;         private Tree(T data)         {             Children = new LinkedList&lt;ITree&lt;T>>();             Data = data;         }         public static Tree&lt;T> FromLookup(ILookup&lt;T, T> lookup)         {             var rootData = lookup.Count == 1 ? lookup.First().Key : default(T);             var root = new Tree&lt;T>(rootData);             root.LoadChildren(lookup);             return root;         }         private void LoadChildren(ILookup&lt;T, T> lookup)         {             foreach (var data in lookup[Data])             {                 var child = new Tree&lt;T>(data) {Parent = this};                 Children.Add(child);                 child.LoadChildren(lookup);             }         }     } }<\/code><\/pre>\n<p>  <\/p>\n<p>Hope that helps. Enjoy coding with the <a href=\"https:\/\/coding-machine.net\" rel=\"nofollow\">Coding Machine<\/a>\u2026<\/p>\n<\/div>\n<\/div>\n<\/div>\n<p><!----><!----><\/div>\n<p><!----><!----><br \/> \u0441\u0441\u044b\u043b\u043a\u0430 \u043d\u0430 \u043e\u0440\u0438\u0433\u0438\u043d\u0430\u043b \u0441\u0442\u0430\u0442\u044c\u0438 <a href=\"https:\/\/habr.com\/ru\/articles\/516596\/\"> https:\/\/habr.com\/ru\/articles\/516596\/<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<div><!--[--><!--]--><\/div>\n<div id=\"post-content-body\">\n<div>\n<div class=\"article-formatted-body article-formatted-body article-formatted-body_version-1\">\n<div xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\">\n<p>One of the very common questions I am getting from <code>.NET<\/code> community is how to configure and use the tree structures in <code>EF Core<\/code>. This story is one of the possible ways to do it.<\/p>\n<p>  <\/p>\n<p>The common tree structures are file tree, categories hierarchy, and so on. Let it be folders tree for example. The entity class will be a <code>Folder<\/code>:<\/p>\n<p>  <\/p>\n<pre><code class=\"cs\">public class Folder {     public Guid Id { get; set; }     public string Name { get; set; }           public Folder Parent { get; set; }     public Guid? ParentId { get; set; }     public ICollection&lt;Folder> SubFolders { get; } = new List&lt;Folder>(); }<\/code><\/pre>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[],"tags":[],"class_list":["post-413547","post","type-post","status-publish","format-standard","hentry"],"_links":{"self":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/413547","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=413547"}],"version-history":[{"count":0,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/413547\/revisions"}],"wp:attachment":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=413547"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=413547"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=413547"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}