メインメニューを開く

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

以前「ツリー構造を持ったレコードをクラス化する」ってのを上げたんだけど…DBだったら自分自身のテーブルにリレーション張ればもっと簡単にできることに、いまさら気がついたんだ…(^_^;)恥ずかしながら、DBの知識が陳腐なことを露呈させちゃったんだよ…

ということで…ツリー構造テーブルのDBにおける定石はおいておいて…テーブルの内容だよ

テーブルの内容

テーブルの内容は前回とほぼ同じなんだ…違うところは自分自身にリレーションしているところだよ。(自分自身にリレーションするので、基本的にルートノードから子ノードへ順に登録しないと、規約違反になっちゃうから注意してね)

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
DBのリレーション
親項目 子項目 参照性合成
(Acccess)
フィールドの連鎖更新
(Access)
レコードの連鎖削除
(Access)
OyakoTable.ID OyakoTable.ParentID チェックをはずす チェックをはずす チェックをはずす
  • 2015/7/28更新 - 「チェックを入れる」となっていましたが「チェックをはずす」に修正しました。チェックをはずさないとレコード削除のときに困ります。
    ちなみに型付データセットのリレーションは「リレーションのみ」にしておきます。
  • 2015/8/5追記 - 要は型付データセット上(データセットデザイナ上)では自由にリレーションを張ってもいいのですが、DB上では最低限のリレーションのみの設定にします。出ないと、データセットとDBで不整合の様な状態になってしまい、実行時にエラーで悩まされることになってしまいます。

ルートノードから順番に登録していたら、テーブルがこんな状態にはならないと思うけど…色々やってたらこうなっちゃったってことにしたいんだ。

ノードのクラス

ノードのクラスは以下のようにしてみたよ。前回と違って、クラス内にレコード持ってみたんだ。そうすれば、レコード内の情報を書き換えるのが楽になるからね。あと、ツリーを上方・下方に行き来できるように、親のクラスも持たせるようにしてみたんだよ。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

//追加
using System.Collections.ObjectModel;

namespace TreeClassSample
{
    public class OyakoClass
    {
        // 親クラスのインスタンスを持つフィールドだよ
        public OyakoClass Parent { set; get; }

        //このクラスの情報が格納されているレコードを持つよ
        public OyakoTable_DataSet.OyakoTableRow OyakoTableRow { set; get; }

        //子供クラスを持つためのコンテナだよ
        //(TreeViewで使うことを前提にObservableCollectionにしてみたよ)
        public ObservableCollection<OyakoClass> Children { set; get; }

        public OyakoClass()
        {
            //コンストラクタでObservableCollectionのインスタンスを生成するよ
            this.Children = new ObservableCollection<OyakoClass>();

            //親クラスの登録はイベントでサクッとしているんだ
            this.Children.CollectionChanged += Children_CollectionChanged;
        }

        //コンテナの内容が変更されたときのイベントハンドラだよ
        void Children_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
        {
            //追加されたときだけ親クラスを登録しているよ
            if(e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Add)
            {
                foreach(OyakoClass f_OyakoClass in e.NewItems)
                {
                    f_OyakoClass.Parent = this;
                }
            }
        }

        //以下、レコード内容にアクセスするためのプロパティだよ
        public Int32 ID
        {
            set { this.OyakoTableRow.ID = value; }
            get { return this.OyakoTableRow.ID; }
        }

        public String Name
        {
            set { this.OyakoTableRow.NodeName = value; }
            get { return this.OyakoTableRow.NodeName; }
        }

        public Int32 ParentID
        {
            set { this.OyakoTableRow.ParentID = value; }
            get { return this.OyakoTableRow.ParentID; }
        }
    }
}

階層構造の生成

生成処理は再起処理で行っているよ。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

//追加
using TreeClassSample.OyakoTable_DataSetTableAdapters;

namespace TreeClassSample
{
    public partial class MainWindow : Window
    {
        private OyakoTable_DataSet m_OyakoTable_DataSet;
        private OyakoClass m_RootOyakoClass;

        public MainWindow()
        {
            InitializeComponent();
        }

        private void Button_Click(object sender, RoutedEventArgs e)
        {
            //レコード取得
            this.m_OyakoTable_DataSet = new OyakoTable_DataSet();
            new OyakoTableTableAdapter().Fill(this.m_OyakoTable_DataSet.OyakoTable);

            //Rootノード取得
            OyakoTable_DataSet.OyakoTableRow[] l_RootOyakoTableRows =
                this.m_OyakoTable_DataSet.OyakoTable
                .Where(x => x.IsParentIDNull() == true)
                .ToArray();

            //取得チェック
            if(l_RootOyakoTableRows.Length != 1)
            {
                MessageBox.Show("「ルートノードがない」か「ルートノードが複数行!」だよ!");
                return;
            }

            //ツリー生成
            this.m_RootOyakoClass = this.CreateOyakoClass(l_RootOyakoTableRows[0]);
        }

        /// <summary>
        /// 「OyakoClass」を作る再起処理だよ
        /// </summary>
        /// <param name="p_OyakoTableRow">RootのDataRowオブジェクトだよ。</param>
        /// <returns>RootのOyakoClassを返却するよ。</returns>
        private OyakoClass CreateOyakoClass(OyakoTable_DataSet.OyakoTableRow p_OyakoTableRow)
        {
            //返却用のOyakoClassを生成するよ。
            OyakoClass l_ResultOyakoClass = new OyakoClass();
            l_ResultOyakoClass.OyakoTableRow = p_OyakoTableRow;

            //再起で子供を作りながら、返却用クラスのChildrenに追加するよ。
            foreach (OyakoTable_DataSet.OyakoTableRow f_OyakoTableRow in p_OyakoTableRow.GetChildRows("OyakoTableOyakoTable"))
            {
                l_ResultOyakoClass.Children.Add(this.CreateOyakoClass(f_OyakoTableRow));
            }

            return l_ResultOyakoClass;
        }
    }
}

前のロジックよりすっきりしたよね!!
前のロジックの使い道を考えたんだけど…CSVのときは使えるかもしれないね…(データセットにCSVを読み込めば上記のロジックで行けちゃうけどね…)