いろいろ備忘録日記

主に .NET とか Go とか Flutter とか Python絡みのメモを公開しています。

ADO.NET入門記-011 (DataRelationの利用 (DataSet, DataTable, DataRelation, ReadXmlSchema, ReadXml, GetChildRows, GetParentRow))


DataSetには、リレーションを設定することができます。
リレーションを設定すると、データベースのように紐付くデータを取得したり、親の行を取得したり出来るようになります。


やり方は簡単で、データセットオブジェクトのRelationsプロパティに対して追加を行ないます。
RelationsプロパティはRelationsCollectionとなっています。

ds.Relations.Add(リレーション名, 親のカラム, 子のカラム);

また、複合キーでのリレーションを行なう場合は、

ds.Relations.Add(リレーション名, 親のカラムの配列, 子のカラムの配列);

となります。


以下サンプルです。
今回、データベースを使うほどでもないので、データソースにはXMLを利用します。
ちなみに、データセットとデータテーブルは、データベース専用のクラスではありません。
これらのクラスは、データソースと関連を持ちません。そして、これらのクラスは
XMLととても相性がいいです。(元々そのように設計されているので)


まずは、XMLスキーマです。
データベースでいうと、テーブル定義に当たるものです。

<?xml version="1.0" encoding="utf-8"?>
<xs:schema 
    id="Memo" 
    targetNamespace="urn:gsf-samples-memo.xsd" 
    elementFormDefault="qualified" 
    xmlns="urn:gsf-samples-memo.xsd" 
    xmlns:mstns="urn:gsf-samples-memo.xsd" 
    xmlns:xs="http://www.w3.org/2001/XMLSchema">

    <xs:element name="Memos">
        <xs:complexType>
            <xs:choice maxOccurs="unbounded">
                <xs:element name="Memo">
                    <xs:complexType>
                        <xs:sequence>
                            <xs:element name="MemoId" type="xs:int" />
                            <xs:element name="Title" type="xs:string" />
                            <xs:element name="Author" type="xs:string" />
                            <xs:element name="Data" type="xs:string" />
                            <xs:element name="CategoryId" type="xs:int" />
                        </xs:sequence>
                    </xs:complexType>
                </xs:element>
                <xs:element name="Category">
                    <xs:complexType>
                        <xs:sequence>
                            <xs:element name="CategoryId" type="xs:int" />
                            <xs:element name="CategoryName" type="xs:string" />
                        </xs:sequence>
                    </xs:complexType>
                </xs:element>
            </xs:choice>
        </xs:complexType>
    </xs:element>
</xs:schema>

次に、使用するデータです。

<?xml version="1.0" encoding="utf-8" ?>
<Memos xmlns="urn:gsf-samples-memo.xsd">
    <Category>
        <CategoryId>1</CategoryId>
        <CategoryName>C#</CategoryName>
    </Category>
    <Category>
        <CategoryId>2</CategoryId>
        <CategoryName>VB2005</CategoryName>
    </Category>
    <Category>
        <CategoryId>3</CategoryId>
        <CategoryName>Java</CategoryName>
    </Category>
    <Memo>
        <MemoId>1</MemoId>
        <Title>タイトル-1</Title>
        <Author>gsf_zero1</Author>
        <Data>メモ-1</Data>
        <CategoryId>1</CategoryId>
    </Memo>
    <Memo>
        <MemoId>2</MemoId>
        <Title>タイトル-2</Title>
        <Author>gsf_zero2</Author>
        <Data>メモ-2</Data>
        <CategoryId>2</CategoryId>
    </Memo>
    <Memo>
        <MemoId>3</MemoId>
        <Title>タイトル-3</Title>
        <Author>gsf_zero3</Author>
        <Data>メモ-3</Data>
        <CategoryId>1</CategoryId>
    </Memo>
    <Memo>
        <MemoId>4</MemoId>
        <Title>タイトル-4</Title>
        <Author>gsf_zero4</Author>
        <Data>メモ-4</Data>
        <CategoryId>3</CategoryId>
    </Memo>
</Memos>

最後にテスト用のクラスです。

using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Common;

using NUnit.Framework;

namespace Gsf.Samples.AdoNet {

    [TestFixture()]
    public class AdoNetSample012{

        [Test()]
        public void データのリレーションが正常に設定されているかどうかの確認(){
            //
            // テストに使用するデータセットを生成。
            // 
            // テスト用に作成したXMLファイルからデータを読み取る。
            //
            DataSet ds = new DataSet();

            ds.ReadXmlSchema("Memo.xsd");
            ds.ReadXml("Memo.xml");

            // 読み込めているかどうかの確認.
            Assert.AreEqual(1,           int.Parse(ds.Tables["Category"].Rows[0]["CategoryId"].ToString()));
            Assert.AreEqual("C#",        ds.Tables["Category"].Rows[0]["CategoryName"].ToString());
            Assert.AreEqual("gsf_zero1", ds.Tables["Memo"].Rows[0]["Author"].ToString());

            ///////////////////////////////////////////////////////////////
            //
            // リレーションの設定.
            //
            DataTable categoryTable = ds.Tables["Category"];
            DataTable memoTable     = ds.Tables["Memo"];
            //
            // データセットに制約を追加.
            //
            // MemoテーブルのCategoryIdにCategoryテーブルを関連させる。
            // 
            // Addメソッドには、リレーション名を指定しないバージョンのメソッドもありますが
            // 後々、リレーション名からデータを取得する事が多いので指定している方が無難です。
            //
            // 通常Addメソッドを実行すると、デフォルトで自動的にForeignKeyConstraintとUniqueConstraintが
            // まだ存在していない場合は作成されます。自動で制約を作成してほしくない場合は、以下のようにします。
            //
            // ds.Relations.Add("FK_CategoryMemo", categoryTable.Columns["CategoryId"], memoTable.Columns["CategoryId"], false);
            //
            ds.Relations.Add("FK_CategoryMemo", categoryTable.Columns["CategoryId"], memoTable.Columns["CategoryId"]);            

            //
            // 制約が作成されていることを確認.
            //
            Assert.AreEqual(typeof(UniqueConstraint),     ds.Tables["Category"].Constraints[0].GetType());
            Assert.AreEqual(typeof(ForeignKeyConstraint), ds.Tables["Memo"].Constraints[0].GetType());

            //
            // データを取得.
            //
            // Memoから属するCategoryを取得する。
            //
            foreach(DataRow row in memoTable.Rows){

                // 所属するカテゴリを取得
                DataRow category = row.GetParentRow("FK_CategoryMemo");

                string displayValue = 
                            string.Format(
                                "memo-id:{0}, title:{1}, author:{2}, data:{3}, category:{4}", 
                                row["MemoId"], 
                                row["Title"], 
                                row["Author"], 
                                row["Data"], 
                                category["CategoryName"]
                            );

                Console.WriteLine(displayValue);
            }

            Console.WriteLine("");

            //
            // データを取得.
            //
            // Categoryから紐付くMemoを取得する。
            //
            foreach(DataRow row in categoryTable.Rows){
                //
                // 紐付くMemoを取得する.
                //
                foreach(DataRow memoRow in row.GetChildRows("FK_CategoryMemo")){

                    string displayValue = 
                            string.Format(
                                "category-id:{0}, category-name:{1}, memo-id:{2}, memo-data:{3}", 
                                row["CategoryId"], 
                                row["CategoryName"], 
                                memoRow["MemoId"], 
                                memoRow["Data"]
                            );

                    Console.WriteLine(displayValue);
                }
            }
        }

    }
}

実行すると以下のようになります。

memo-id:1, title:タイトル-1, author:gsf_zero1, data:メモ-1, category:C#
memo-id:2, title:タイトル-2, author:gsf_zero2, data:メモ-2, category:VB2005
memo-id:3, title:タイトル-3, author:gsf_zero3, data:メモ-3, category:C#
memo-id:4, title:タイトル-4, author:gsf_zero4, data:メモ-4, category:Java

category-id:1, category-name:C#, memo-id:1, memo-data:メモ-1
category-id:1, category-name:C#, memo-id:3, memo-data:メモ-3
category-id:2, category-name:VB2005, memo-id:2, memo-data:メモ-2
category-id:3, category-name:Java, memo-id:4, memo-data:メモ-4

ちゃんとリレーションが動作している事が確認できますね。