ツリー構造を持ったレコードをクラス化する2
以前「ツリー構造を持ったレコードをクラス化する」ってのを上げたんだけど…DBだったら自分自身のテーブルにリレーション張ればもっと簡単にできることに、いまさら気がついたんだ…(^_^;)恥ずかしながら、DBの知識が陳腐なことを露呈させちゃったんだよ…
ということで…ツリー構造テーブルのDBにおける定石はおいておいて…テーブルの内容だよ
テーブルの内容
テーブルの内容は前回とほぼ同じなんだ…違うところは自分自身にリレーションしているところだよ。(自分自身にリレーションするので、基本的にルートノードから子ノードへ順に登録しないと、規約違反になっちゃうから注意してね)
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 |
親項目 | 子項目 | 参照性合成 (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を読み込めば上記のロジックで行けちゃうけどね…)