Enterprise ArchitectのクラスモデルからオートコードしてXMLへシリアライズ化する話


目的

UMLでモデル化したクラス構造からスケルトンを生成し、さらにそれ自体を手を加えずにXMLシリアライザでXML化できるようにする。

EAはプロフェッショナル版です。
全体の処理フローは以下の通り。

XMLクラスモデル作成

適当にパッケージを作成してダイアグラムの追加からXMLスキーマ図を選択します。
以下の通り作成しました。

接続線はデフォルトで提示されている「汎化」と「関係」のみ使用しました。
また、多重度もしっかり定義します。
この多重度はコード化したときにリストに変換できます。

例えば「SuperMyClass」に定義されているような「name」とか「id」の細かい設定はツールボックスからElementやAttributeをつまんでcomplexTypeの上へもっていくと追加できます。
ただし後述しますが、AttributeのXMLシリアライズ化は成功していません。

また、各クラスにはメソッドも追加できます。
これらはXML化する際には無視されます。

ここでは以下のルールを守るように作成するとうまくいくことがわかりました。
1.自己のクラスに関連線を直接回帰させない。
C#コードへ落とした時にこれらのクラス名はそのままクラス名に、関連線で参照可となっている部分はその参照先のクラス名がそのままアクセサ名になります。
なので、自己に接続線をつなげるとクラス名と参照先のアクセサ名が同じになりシンタックスエラーを起こします。
なぜアクセサ名を変えないかというと自動でコード生成するときに面倒だからです。
自己のクラスと同じインスタンスを参照したい場合は図で言う「Children」みたいなクラスを間に挟みます。
まあただこれだと双方向参照はできないので、双方向に参照したい場合は最低2つのクラスを間に挟む必要があるかもしれません。

2.基本的にXSDcomplexTypeのみを配置する。

C#クラスモデル変換

モデルの変換からC#を選べばすぐ変換できます。
自動生成なので縦長で見ずらいのは仕方ないです。

C#クラススケルトン生成

csコードを作成するだけならデフォルトのままでもすぐ生成できます。
しかし、XMLシリアライズを考えると少し手間が必要です。
以下は最終的に吐き出した「MyClass」のスケルトンですが、
「[System.Xml.Serialization.XmlRoot("MyClass")]」や
「[System.Xml.Serialization.XmlElement("Children")]」
は手動でコード生成テンプレートをいじりました。

MyClass.cs
namespace XMLClasses {
    [System.Xml.Serialization.XmlRoot("MyClass")]
     public class MyClass : SuperMyClass {

        private XMLClasses.Children m_Children;
        private XMLClasses.SubChildren m_SubChildren;

        public MyClass(){

        }

        ~MyClass(){

        }

        [System.Xml.Serialization.XmlElement("Children")]

        public Children Children{
            get{
                return m_Children;
            }
            set{
                m_Children = value;
            }
        }

        public void ShowSubChildren(){

        }

        [System.Xml.Serialization.XmlElement("SubChildren")]

        public SubChildren SubChildren{
            get{
                return m_SubChildren;
            }
            set{
                m_SubChildren = value;
            }
        }

    }//end MyClass

}//end namespace XMLClasses

まずRootに対しては簡単で以下を仕込めば終わりです。
C#クラスがXSDcomplexTypeステレオタイプの場合には別のテンプレートを適用するようになります。

これはクラス名をルートノード名にすることを意味しています。

また、そのプロパティもシリアライズできるように以下を追加します。
今度はOperation Declarationのpropertyステレオタイプに対するテンプレートを変更します。
本当はXmlAttributeとXmlElementを区別させたいのですが、
大元の属性を参照して分岐させないといけないので実装していません。
なので生成されるXMLは想定と違うものが出てきます。
しかし、デシリアライズしたときの構造は変わりません。

C#コード作成

検証用にシリアライズ、デシリアライズするコードを作成します。
適当にクラスを生成してXMLでシリアライズした後に生成したXMLをデシリアライズして新しいインスタンスを生成します。

Program.cs
    class Program
    {
        static void Main(string[] args)
        {
            MyClass root = new MyClass();
            root.Name = "class1";
            root.Id = 1;


            root.Children = new Children();
            root.Children.MyClass = new List<MyClass>();
            root.Children.MyClass.Add(new MyClass());
            root.Children.MyClass[0].Name = "class2";
            root.Children.MyClass[0].Id = 2;
            root.Children.MyClass.Add(new MyClass());
            root.Children.MyClass[1].Name = "class3";
            root.Children.MyClass[1].Id = 3;
            root.Children.MyClass.Add(new MyClass());
            root.Children.MyClass[2].Name = "class4";
            root.Children.MyClass[2].Id = 4;

            FileStream stream = new FileStream(@"test.xml", FileMode.Create);
            StreamWriter writer = new StreamWriter(stream, System.Text.Encoding.UTF8);

            XmlSerializer serializer = new XmlSerializer(typeof(MyClass));
            serializer.Serialize(writer, root);

            writer.Flush();
            writer.Close();


            FileStream fs = new FileStream(@"test.xml", FileMode.Open);

            XmlSerializer deserializer = new XmlSerializer(typeof(MyClass));

            MyClass deserMyClass = (MyClass)serializer.Deserialize(fs);

        }
    }

XMLは以下のものが生成されました。
想定通りのファイルになっています。

test.xml
<?xml version="1.0" encoding="utf-8"?>
<MyClass xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <Id>1</Id>
  <Name>class1</Name>
  <Children>
    <MyClass>
      <Id>2</Id>
      <Name>class2</Name>
    </MyClass>
    <MyClass>
      <Id>3</Id>
      <Name>class3</Name>
    </MyClass>
    <MyClass>
      <Id>4</Id>
      <Name>class4</Name>
    </MyClass>
  </Children>
</MyClass>

デバッグしてデシリアライズした構造も見てみます。
問題なくデシリアライズされています。

次はAttributeも扱えるようにしたいですね。
(Attributeにした方が処理が早かったりするのだろうか・・・)
以上