Most efficient method of self referencing tree using Entity Framework

后端 未结 6 2098
萌比男神i
萌比男神i 2020-11-28 02:12

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

6条回答
  •  [愿得一人]
    2020-11-28 02:47

    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.

提交回复
热议问题