使用C#/Linq将SQL Server中扁平的分层数据转换为结构化JSON对象

4

我正在开发一个MVC应用程序,从一个结构如下的SQL Server表中检索数据:

+-----------------------------------+
| Id | Name   | Hierarchy   | Depth |
|-----------------------------------|
| 01 | Justin | /           |     0 |
| 02 | Chris  | /1          |     1 |
| 03 | Beth   | /1/1        |     2 |
+-----------------------------------+

Hierarchy列中的示例数据是hierarchyid数据类型的字符串表示,Depth列使用hierarchyid::GetLevel()方法进行计算。

使用Entity Framework 4.1,我将上述表映射到以下类:

public class Node {
    public int Id { get; set; }
    public string Name { get; set; }
    public string HierarchyPath { get; set; } // String representation of the hierarchyid
    public int Depth { get; set; }
}

我希望使用这些信息,通过JS可视化工具包向用户展示层次结构的图形表示。该工具需要数据进行结构化处理:
var node = {
    id: 1,
    name: 'Justin'
    children: [{
        id: 2,
        name: 'Chris',
        children: [{
            id: 3,
            name: 'Beth',
            children: []
        }]
    }]
}

我在开发中遇到了一个问题,就是如何将我的模型列表转换为结构化的JSON对象。有什么建议吗?


"/1/1"在层次路径中是如何工作的?它是否依赖于子节点按ID排序?(想象一下,如果我们有另一个深度为1的名为“Foo”的节点,我们怎么知道Beth是Foo的子节点还是Chris的子节点?) - Jon Skeet
路径与ID列无关,而是指节点在层次结构中的位置。深度为1的第二个节点将具有路径/2/2的第一个子节点将具有路径/2/1,然后是第二个子节点/2/2,以此类推。更好的示例可以在此处找到:http://technet.microsoft.com/en-us/library/bb677195.aspx。 - Justin Rusbatch
啊,我明白了……我想是吧。这有点烦人。 - Jon Skeet
2个回答

8

编辑:我现在没有时间修改下面的回答,但是考虑到问题中的额外信息,我猜测您想保留一个 Dictionary<int, HierarchicalNode> 而不是一个 List<HierarchicalNode>,这样您就不需要依赖任何排序......


我建议先忘记JSON表示,专注于构建内存中层次结构的POCO表示。为此,我会使用以下代码:

class HierarchicalNode
{
    private readonly List<HierarchicalNode> children =
        new List<HierarchicalNode>();        
    public List<HierarchicalNode> Children { get { return children; } }

    private readonly string name;
    public string Name { get { return name; } }

    private readonly int id;
    public int Id { get { return id; } }

    public HierarchicalNode(string name, int id)
    {
        this.name = name;
        this.id = id;
    }
}

然后按以下方式构建树形结构:
// Make sure we get everything in a sensible order, parents before children
var query = context.Nodes.OrderBy(x => x.Depth);

var root = new HierarchicalNode("Root", 0);
foreach (var node in query)
{       
    var current = root;
    foreach (string part = node.HierarchyPath.Split(new[] {'/'},
                                   StringSplitOptions.RemoveEmptyEntries))
    {
        int parsedPart = int.Parse(part);
        current = current.Children[parsedPart - 1];
    }
    current.Children.Add(new HierarchicalNode(node.Name, node.Id));
}

@Justin:很高兴能帮忙,抱歉我无法提供完整的代码;考虑到后来的信息,我假设你需要稍微修正一下。 - Jon Skeet
3
谢谢您发布这个问题。这正是我想要的。有没有可能发布解决方案?如果您能发布解决方案,那将非常棒!!! - SharpCoder
@Justin 根据您的已回答勾选,请您提供正确的代码。不幸的是,上述代码并不能正常工作。 - Mohammad
@Mohammad:你能澄清一下“不正确工作”的意思吗?如果没有提供更多信息,很难修复它。 (请记住这已经将近9年了。如果Justin不再手头有代码,我也不会感到惊讶。) - Jon Skeet
@Jon,我知道这个问题很老了,但它无法正常工作。我稍后会在这里放置数据错误。 - Mohammad
显示剩余4条评论

0

我知道这个问题很旧,但是我遇到了这个问题,并且在任何地方都没有找到确切的解决方案。我能够在 @Jon 的代码基础上构建并创建JSON,就像OP首先将其转换为POCO一样。如果有帮助,我在这里发布。

这个解决方案的前提条件是您知道数据的最大级别/深度,然后您可以做以下操作:

  var nodesList = context.Nodes.OrderBy(x => x.Depth).ToList();
            var hierarchalData = new HierarchicalNode();
            foreach (var node in nodesList)
            {
                if (node.Depth == 1)
                {
                    // create the parent node
                    hierarchalData = new HierarchicalNode(node.Name, node.Id);
                }
                else
                {
                    // create the child node object
                    var childNode = new HierarchicalNode(node.Name, node.Id);

                    // split the hierarchy into an array to get parent indexes
                    var splitHierarchy = node.HierarchyPath.Split("/").Skip(1).SkipLast(1).ToArray();
                    switch (node.Depth)
                    {
                        case 2:
                            hierarchalData.Children.Add(childNode);
                            break;
                        case 3:
                            var lastParentIndex = Convert.ToInt32(splitHierarchy[splitHierarchy.Length - 2]) - 1;
                            hierarchalData.Children[lastParentIndex].Children.Add(childNode);
                            break;
                        case 4:
                            var firstParentIndex = Convert.ToInt32(splitHierarchy[splitHierarchy.Length - 3]) - 1;
                            var lastParentIndex1 = Convert.ToInt32(splitHierarchy[splitHierarchy.Length - 2]) - 1;
                            hierarchalData.Children[firstParentIndex].Children[lastParentIndex1].Children.Add(childNode);
                            break;
                        default:
                            break;
                    }
                }
            }

我知道这种方法可能是暴力的,但对我来说做到了。 在我的情况下,层数为7。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接