So I have a SQL table which is basically
ID, ParentID, MenuName, [Lineage, Depth]
The last two columns are auto-computed to help with sear
I knew that there must be something wrong with this solution. It is not simple. Using this solution, EF6 require another package of hacks to manage a simple tree (fe. deletions). So finally I've found a simple solution but combined with this approach.
First of all leave entity simple: just Parent and list of Children is enough. Also mapping should be simple:
HasOptional(x => x.Parent)
.WithMany(x => x.Children)
.Map(m => m.MapKey("ParentId"));
HasMany(x => x.Children)
.WithOptional(x => x.Parent);
Then add migration (code first: migrations: package console: Add-Migration Hierarchy) or in other ways a stored procedure:
CREATE PROCEDURE [dbo].[Tree_GetChildren] (@Id int) AS
BEGIN
WITH Hierachy(ChildId, ParentId) AS (
SELECT ts.Id, ts.ParentId
FROM med.MedicalTestSteps ts
UNION ALL
SELECT h.ChildId, ts.ParentId
FROM med.MedicalTestSteps ts
INNER JOIN Hierachy h ON ts.Id = h.ParentId
)
SELECT h.ChildId
FROM Hierachy h
WHERE h.ParentId = @Id
END
Then when you will try to receive your tree nodes from database just do it in two steps:
//Get children IDs
var sql = $"EXEC Tree_GetChildren {rootNodeId}";
var children = context.Database.SqlQuery(sql).ToList();
//Get root node and all it's children
var rootNode = _context.TreeNodes
.Include(s => s.Children)
.Where(s => s.Id == id || children.Any(c => s.Id == c))
.ToList() //MUST - get all children from database then get root
.FirstOrDefault(s => s.Id == id);
It all. This query helps you to get a root node and load all children. Without playing with introducing Ancestors and Descendants.
Remember also when you will try to save sub-node, then do it just this way:
var node = new Node { ParentId = rootNode }; //Or null, if you want node become a root
context.TreeNodess.Add(node);
context.SaveChanges();
Do it that way, not by adding children to root node.