ツリー構造を持ったレコードをクラス化する

提供: とある社畜の頭脳整理
移動先: 案内検索

なんだか、表現がややっこしいんだけど…ツリー構造を持ったレコード群をクラス化してみるよ。

ツリー構造を持ったレコード

ツリー構造をRDBに持たせるときに良くやる方法で、1つのフィールドに親のレコードのキーを設定するってことをやるんだよね。イメージ的には以下の様な感じ…

root ┬ 親1 ┬ 子1-1
   │    │
   │    ├ 子1-2
   │    │
   │    └ 子1-3
   │
   └ 親2 ┬ 子2-1 ┬ 孫2-1-1
        │      │
        │      └ 孫2-1-2
        └ 子2-2

って構造の場合…

ID(key) 名前 親ID
0 root -1
1 親1 0
2 子1-1 1
3 子1-2 1
4 子1-3 1
5 親2 0
6 子2-1 5
7 孫2-1-1 6
8 孫2-1-2 6
9 子2-2 5

ってレコードを作製するのが定石なんだよ。初めてこれを見ると「レコードに子供をもてばいいじゃん!」ってなりがちなんだけど…そうすると1つのノードで複数レコード(たとえば「親1」で3レコード)必要になっちゃうんだよね…だから、一般的にRDBに階層構造を保存するときは、「親のIDを持つ」ってことをするんだよ。

で、このレコードを上の階層化されたクラス群に復元しようとすると…(?o?)ってなっちゃうんだ。上記の表の様にレコードの順番がきれいに並んでいれば問題ないけど…これが…

ID(key) 名前 親ID
0 子1-3 2
1 親2 3
2 親1 3
3 root -1
4 子1-1 2
5 孫2-1-2 7
6 子1-2 2
7 子2-1 1
8 孫2-1-1 7
9 子2-2 1

ってなっちゃってたら、「親がなかったら親ができるまで待って、親ができたら保留にしてた分を全部登録して…」なんてことになっちゃう… これをC#でさくっと綺麗に作ろうと思うんだ。

レコードのクラス

型付データセットを利用していることを前提にするよ。

OyakoTable
ID : int Name : String ParentID : int
0 子1-3 2
1 親2 3
2 親1 3
3 root -1
4 子1-1 2
5 孫2-1-2 7
6 子1-2 2
7 子2-1 1
8 孫2-1-1 7
9 子2-2 1

このテーブルのレコードクラスをはOyakoTableRowってことにするよ。それぞれのフィールドの名前や型は上の表のとおりって仮定するよ。

ノードのクラス

ノードのクラスは以下のようにするよ。あえて、IDは持たないようにしているんだ。これは、クラス化するとIDを持たなくてもインスタンスをそれぞれが持てばよくなるからなんだよ。

public class Node
{
    private List<Node> m_NodeList;
    public String Name { get; set; }

    public Node()
    {
        this.m_NodeList = new List<Node>();
    }

    public Node AddNode(Node p_Node)
    {
        this.m_NodeList.Add(p_Node);
        return p_Node;
    }
}

サンプルは上位階層から下位階層への参照しかできない構造にしているよ。もし、下位から上位への参照がほしい場合は親のインスタンスが持てるようにしてね。

階層構造の生成

ポイントはDictionaryクラスを使うことかな…(致命的なロジックミスがあったので修正しました - 2015/01/30)

public Node Create(OyakoTableRow[] p_OyakoTableRow)
{
    Dictionary<Int32, Node> l_NodeDic = new Dictionary<int, Node>();
    Node l_ResultNode = null;

    foreach(OyakoTableRow f_OyakoTableRow in p_OyakoTableRow)
    {
        Node l_Node = null;
        if (l_NodeDic.TryGetValue(f_OyakoTableRow.ID, out l_Node) == false)
        {
            l_Node = new Node();
            l_NodeDic.Add(f_OyakoTableRow.ID, l_Node);

        }
        l_Node.Name = f_OyakoTableRow.Name;
        

        if (f_OyakoTableRow.ParentID == -1)
        {
            l_ResultNode = l_Node;
        }
        else
        {
            Node l_ParentNode = null;
            if (l_NodeDic.TryGetValue(f_OyakoTableRow.ParentID, out l_ParentNode) == false)
            {
                l_ParentNode = new Node();
                l_NodeDic.Add(f_OyakoTableRow.ParentID, l_ParentNode);
            }
            l_ParentNode.AddNode(l_Node);
        }

    }

    return l_ResultNode;
}

ただ…ツリーが小さいときは良いんだけど…馬鹿でかくなったときにはこの方法は勧めません…(すべてのクラスがDictionaryクラスに入っているし、すべてのクラスを生成しちゃっているので…)