![見出し画像](https://assets.st-note.com/production/uploads/images/169336802/rectangle_large_type_2_2853b24a8c4d781a5ab69ad10f167d4e.png?width=1200)
116. 振舞いのコードを生成する
はじめに
前回まで、BridgePoint で作成した健診受診のモデルから、Azure Digital Twins の Twin Model を定義する DTDL の生成を解説していました。
YaccLexTool の文字化け問題の暫定策が機能したので、BridgePoint で作成した状態モデルも含めた全ての記述から、振舞いも含めた C# による実装コードを生成するステップに進みます。
ちなみにYaccLexTool は私の Issue 報告に対して対応はしてくれたようですが、確認に手間取りそうなので、暫定版で進めることにします。
C# コード生成
概念モデルからの C# コード生成は、
GitHub - kae-made/domainmodel-code-generator-csharp: Domain Model Code Generator for C#
で公開している C# ジェネレータを使います。
Tools
![](https://assets.st-note.com/img/1736398608-gcDNeB25MvulWZ89p3hU6wos.png?width=1200)
Kae.XTUML.Tools.Generator.CodeOfDomainModelCsharp が、C#コードジェネレータの本体を実装するライブラリの開発用プロジェクトです。template フォルダーの下に、概念モデルを C# に変換する T4 Template で書かれた変換ルールファイルがあります。
![](https://assets.st-note.com/img/1736400074-ifBDlGVQ8FzgnHMpE4WyUdk6.png?width=1200)
変換ルールを記述する T4 Template のファイル(拡張子:.tt)の記述時はコードに置き換える部分でインテリセンスが効かないので、極力シンプルにして実装は Code.cs にまとめるようにしています。
C#コードジェネレータ本体は、xtuml-ooa-of-ooa-library で公開されている、BridgePoint が出力した概念モデル記述ファイルのパーサーライブラリの Kae.XTUML.Tools.CIModelResolver を NuGet パッケージとして組み込んでいます。
![](https://assets.st-note.com/img/1736400460-kIfvMNaex9gCdEzDtXpnhZYm.png?width=1200)
Kae.CIM.MetaModelCIMofCIM は、読み込んだ概念モデルの内容をメモリ上に保持するためのライブラリです。CIClassBases.cs、CIClassDefs.cs、CIMOOAofOOAClass.cs は、Kae.XTUML.Tools.CIModelResolver を使って、BridgePoint のメタモデル定義ファイル(xtumlmc_schema.sql)から自動生成しています。
このあたりの仕組みの詳細は、”Technique of Transformation” で詳しく解説しているので、そちらをご一読ください。
今回は、WPFアプリ形式の WpfAppCsharpGenerator を使うことにします。
Generation
Visual Studio で開発した WpfAppCsharpGenerator でコード生成する前に、BridgePoint で、生成対象の概念モデルを記述したプロジェクトを Build します。
![](https://assets.st-note.com/img/1736401220-624aApNfJdnly8vQMF5VGk0i.png?width=1200)
Build を実行すると、状態のエントリアクションも記述された、ProjectName.xtuml というファイルが、プロジェクトの情報が保持されたフォルダーの下の gen/code_generation に生成されます。今回は HealthcareStudy というプロジェクト名なので、HealthcareStudy.xtuml というファイルが出来上がります。
WpfAppCsharpGenerator を起動し、
![](https://assets.st-note.com/img/1736401705-ucqHPtfwn2ZCByUpmE5QoFLh.png?width=1200)
のように設定して”Generate C# Code”をクリックします。
![](https://assets.st-note.com/img/1736401896-TZJ40lKPDAtafzQI2mCpLGW6.png?width=1200)
生成が終わると、右側に生成先として指定したフォルダーの内容が表示されます。
モデルの記述ミス対応
この状態になるまでに BridgePoint 側のモデルの記述の間違いのせいでうまくいきませんでした。間違いは4つありました。
一つ目は、概念情報モデル上に、同じ名前の概念クラスが二つあった点です。どうやらモデルを修正している間に同じ名前の概念クラスを作ってしまい、不必要な側の削除を忘れていたようです。
ひとつの概念ドメインに定義された概念クラスはすべて異なる名前でなければならないというのが概念モデリングのルールです。
具体的には、”予約割当(HC_RA)”です。よくよく見ると、適切なリレーションシップも定義されていなかったので、そちらも追加し、いらないほうを削除しました。
![](https://assets.st-note.com/img/1736402496-wTdUiSlBqJKbXraWzxAONpYD.png?width=1200)
二つ目は、識別子特徴値を持たない概念クラスがあることによるものでした。これは一つ目の修正が甘かったことが原因の一つ。
上の図には、”{I}”が付与された特徴値が見当たりません。同じ問題が”健診計測機器管理(HC_MCDM)”にもありました。どちらもモデルの見直しの際に新たに追加した概念クラスでした。
![](https://assets.st-note.com/img/1736403061-oP0d2U87HBCrhin5M3SZsYAu.png?width=1200)
こちらも、概念インスタンスの一意性を表現するためには、概念クラスには識別子特徴値が一つ以上必要という概念モデリングのルールを破っています。
予約割当(HC_RA)
MPLCID、LGDID
健診計測器管理(HC_MCDM)
MPLCID、LGDID
それぞれ、二つの特徴値が識別子特徴値なので、{I} をBridgePoint 上で付与して修正完了。
![](https://assets.st-note.com/img/1736403121-S7vkwb39KDUIgsAncXtopdBH.png?width=1200)
ちなみに、識別子特徴値がないと、生成されたファイルのうち、Adaptor/HealthCareCheckAdaptor.cs というファイルで、
修正前の生成コード
case "HC_RA":
var instanceOfHC_RATempSet = domainModel.InstanceRepository.GetDomainInstances("HC_RA").Where(selected => ();
if (domainModel.InstanceRepository.ExternalStorageAdaptor != null) instanceOfHC_RATempSet = domainModel.InstanceRepository.ExternalStorageAdaptor.CheckInstanceStatus(CIMHealthCareCheckLib.DomainName, "HC_RA", instanceOfHC_RATempSet, () => { return $"MPLCID = '{invSpec.Identities["MPLCID"]}' AND LGDID = '{invSpec.Identities["LGDID"]}'"; }, () => { return DomainClassHC_RABase.CreateInstance(domainModel.InstanceRepository, logger); }, "any").Result;
var instanceOfHC_RA = (DomainClassHC_RA)instanceOfHC_RATempSet.FirstOrDefault();
修正後の生成コード
case "HC_RA":
var instanceOfHC_RATempSet = domainModel.InstanceRepository.GetDomainInstances("HC_RA").Where(selected => (((DomainClassHC_RA)selected).Attr_MPLCID == invSpec.Identities["MPLCID"] && ((DomainClassHC_RA)selected).Attr_LGDID == invSpec.Identities["LGDID"]));
if (domainModel.InstanceRepository.ExternalStorageAdaptor != null) instanceOfHC_RATempSet = domainModel.InstanceRepository.ExternalStorageAdaptor.CheckInstanceStatus(CIMHealthCareCheckLib.DomainName, "HC_RA", instanceOfHC_RATempSet, () => { return $"MPLCID = '{invSpec.Identities["MPLCID"]}' AND LGDID = '{invSpec.Identities["LGDID"]}'"; }, () => { return DomainClassHC_RABase.CreateInstance(domainModel.InstanceRepository, logger); }, "any").Result;
var instanceOfHC_RA = (DomainClassHC_RA)instanceOfHC_RATempSet.FirstOrDefault();
という生成ミスが発生します。
3つ目は、ひとつの概念クラスに同じ名前の特徴値が存在していた、という間違い。
![](https://assets.st-note.com/img/1736403625-tva1s3UTCdJG6AgyfOqYNemB.png?width=1200)
健診計測器(HC_MCD)に LGDID という特徴値が二つあります。R4 と R10 という二つのリレーションシップの参照属性を追加するときに、結果的にめぐりめぐって同じ名前の特徴値になってしまっていました。一つの概念クラスに複数のリレーションシップを定義するときには往々にして発生しがちなので注意が必要。
こちらは Model Explorer で、どちらか一方の特徴値を右クリックして”Combine”を選択すると解決。
![](https://assets.st-note.com/img/1736403912-7Mb4SqvxF9AeduaknDXTLZHw.png?width=1200)
これは、あくまでもリレーションシップをフォーマライズするときのミスであり、不用意に、というか、いい加減なモデリングですごく沢山の特徴値を持つ概念クラスが2、3個だけみたいな、くそいい加減なモデリングをしたときの技ではないことに注意が必要です。その解決策は、モデル化対象の意味の場を深く深~く理解することだけ。
最後は、算術可能な特徴値の実装忘れ。”対象者(HC_T)”という概念クラスは、
![](https://assets.st-note.com/img/1736404588-Og85GaD9kzANeqHmwJWro7nd.png?width=1200)
は、”年齢 {M}” という特徴値を持っていて、これは、この特徴値を参照したときの日時と、”生年月日”から自動計算できるよねってこと。この特徴値の計算式を定義するのをすっかり忘れていました。
追加した計算式は以下の通り。
today = TIM::current_date() ;
yearOfToday = TIM::get_day( date:today ) ;
monthOfToday = TIM::get_month( date:today ) ;
dayOfToday = TIM::get_day( date:today ) ;
yearOfBirth = TIM::get_year( date:SELF.生年月日 ) ;
monthOfBirth = TIM::get_month( date:SELF.生年月日 ) ;
dayOfBirth = TIM::get_day( date:SELF.生年月日 ) ;
age = yearOfToday - yearOfBirth ;
IF monthOfToday <= monthOfBirth
IF monthOfToday < monthOfBirth OR ( monthOfToday == monthOfBirth AND dayOfToday < dayOfBirth )
age = age - 1 ;
END IF ;
END IF ;
SELF.年齢 = age ;
この概念クラスは、状態モデルも持っています。
![](https://assets.st-note.com/img/1736404867-WnKF0JCTu8tH7SskxM9Uy3hD.png?width=1200)
この状態モデルには、”HC_T1:登録”という生成イベントが定義されています。算術可能な特徴値の計算式を記述していないと、このイベントを生成するアクション記述が BridgePoint 上でエラーになるのでご注意。
ちなみに、概念クラスに定義されたオペレーションには何もアクションを書かなくても生成時は関係なし。
生成されたファイル
WpfAppCsharpGenerator は、Visual Studio 2022 で開くことが可能な C# プロジェクト一式を生成します。
![](https://assets.st-note.com/img/1736404425-gsSPtrJlB3VybHm9Oe4xF0ZK.png)
さて。生成されたコードの中で、概念情報モデルの概念クラスにオペレーションが定義されている場合で、その内容のアクション記述を書いていない場合は、Visual Studio 2022 等で、そのオペレーションの実装を手書きで実装しなければなりません。そのオペレーションが出力を持っている場合には、Visual Studio でのビルド時にエラーになります。例えば、”被検者管理(HC_TM)”の”管理番号決定”オペレーションは、
![](https://assets.st-note.com/img/1736405353-mzxtlIfAbqhyRMa6SOYZn0KP.png?width=1200)
// ------------------------------------------------------------------------------
// <auto-generated>
// This file is generated by tool.
// Runtime Version : 1.0.0
//
// </auto-generated>
// ------------------------------------------------------------------------------
using System;
using System.Collections.Generic;
using System.Linq;
using Kae.StateMachine;
using Kae.DomainModel.Csharp.Framework;
using Kae.DomainModel.Csharp.Framework.Adaptor.ExternalStorage;
namespace HealthCareCheck
{
partial class DomainClassHC_TMBase
{
public string 管理番号決定()
{
// TODO : Let's write code!
// Action Description on Model as a reference
var changedStates = new List<ChangedState>();
// Generated from action description
}
}
}
概念モデル上で書かないということは、BridgePoint で書くより、生成したコードで書いたほうが効率が良いということなので、これは良しとして…
ここから先は
Azure の最新機能で IoT を改めてやってみる
2022年3月にマイクロソフトの中の人から外の人になった Embedded D. George が、現時点で持っている知識に加えて、頻繁に…
この記事が気に入ったらチップで応援してみませんか?