はじめに
前回は CodeDefiner のエントリポイントと全体フローを見ました。今回は、コード自動生成の入力データとなる定義ファイルとテンプレートの構造を深く掘り下げます。
本連載で参照するソースコードはすべて Implem/Implem.Pleasanter リポジトリのものです。リンクはコミットハッシュベースのパーマリンクを使用しています。
| 回 | テーマ |
|---|---|
| 第1回 | 全体像:エントリポイント・コマンド体系・初期化フロー |
| 第2回(本記事) | 定義ファイルとテンプレートシステム |
| 第3回 | テンプレート展開エンジン |
| 第4回 | コードマージシステム |
| 第5回 | RepeatType別のコード生成ハンドラ |
| 第6回 | プレースホルダー置換と型変換 |
定義ファイルの格納場所
コード自動生成に関わる定義ファイルは、以下のディレクトリに格納されています。
Implem.Pleasanter/App_Data/Definitions/
├── Definition_Code/ ← コード生成テンプレートと設定(本記事の主題)
│ ├── *.json ← 定義の属性(約876ファイル)
│ └── *_Body.txt ← テンプレート本体(約875ファイル)
└── Definition_Column/ ← カラム定義(約460ファイル)
└── *.json
Definition_Code の2種類のファイル
Definition_Code/ ディレクトリには、1つのコード定義につき最大2つのファイルがペアで存在します。
JSON ファイル(定義の属性)
JSONファイルには、テンプレートのメタ情報(出力先パス、繰り返しタイプ、フィルタ条件など)が記述されています。
{
"Id": "Base",
"OutputPath": "Models\\Shared\\_BaseModel.cs",
"Source": "Mvc"
}
{
"Id": "Base_Property",
"Indent": "2",
"Separator": "\\r\\n",
"NotCalc": "1",
"Exclude": "SiteSettings"
}
Body.txt ファイル(テンプレート本体)
_Body.txt で終わるファイルには、実際のC#コードテンプレートが記述されています。テンプレート中に <!--子定義ID--> 形式のプレースホルダーや #置換名# 形式の変数プレースホルダーが含まれます。
if (ss.PermissionForCreating != null)
{
ss.SetPermissions(
context: context,
referenceId: #ModelName#Id);
}
ペアリングの規則
JSONファイルの Id と Body.txt ファイル名の対応は次のとおりです。
| JSONファイル | Body.txtファイル |
|---|---|
Base.json(Id: "Base") |
Base_Body.txt |
Model_ReloadPermissions.json |
Model_ReloadPermissions_Body.txt |
Body.txt が存在しない定義もあります。その場合は、JSON 内の Body プロパティに直接テンプレート内容を記述するか、子定義を組み合わせた展開のみを行います。
定義ファイルの読み込みの仕組み
定義ファイルの読み込みは、XlsIo クラスが担当しています。歴史的にExcelファイル(.xlsm)から読み込んでいた名残で XlsIo(Excel I/O)という名前ですが、現在のバージョンではJSONファイルベースの読み込みが主流です。
Body.txt の特別な扱い
*_Body.txt ファイルは、対応する定義の Body カラムにセットされます。
// ReadDefinitionFiles メソッド内
if (file.Name.EndsWith("_Body.txt"))
{
// ファイル名から定義IDを逆引き
// 対応する行の "Body" カラムに内容を設定
}
これにより、JSONファイルでメタ情報を、テキストファイルでテンプレート本体を、それぞれ管理しやすい形式で記述できるようになっています。
CodeDefinition クラスの構造
読み込まれた定義は CodeDefinition クラスのインスタンスとして格納されます。
基本プロパティ
| プロパティ | 型 | 説明 | 例 |
|---|---|---|---|
Id |
string | 定義の一意識別子 |
"Base", "Model_Property"
|
Body |
string | テンプレート本体 | C#コードテンプレート |
OutputPath |
string | 出力ファイルのパス | "Models\\#ModelName#\\..." |
Source |
string | 定義の種別 |
"Mvc", "Def"
|
RepeatType |
string | 繰り返しの種類 |
"Table", "Column" 等 |
MergeToExisting |
bool | 既存コードにマージするか |
true / false
|
テンプレート制御プロパティ
| プロパティ | 型 | 説明 |
|---|---|---|
Indent |
int | テンプレートのインデント深度 |
Separator |
string | 繰り返し展開時の区切り文字(例:"\\r\\n") |
Order |
string | カラムの並び順を指定するフィールド名 |
NoSpace |
bool | 空行を除去するか |
ReplaceOld |
string | 展開後に置換する旧文字列 |
ReplaceNew |
string | 展開後に置換する新文字列 |
フィルタプロパティ(Include / Exclude 系)
特定のテーブルやカラムのみにコードを生成する、あるいは除外するためのフィルタが多数定義されています。
| プロパティ | 型 | フィルタ対象 | 説明 |
|---|---|---|---|
Include |
string | テーブル名/カラム名 | カンマ区切りで包含指定 |
Exclude |
string | テーブル名/カラム名 | カンマ区切りで除外指定 |
IncludeTypeName |
string | DBの型名 | 特定の型名だけに適用 |
ExcludeTypeName |
string | DBの型名 | 特定の型名を除外 |
IncludeTypeCs |
string | C#の型名 | 特定のC#型だけに適用 |
ExcludeTypeCs |
string | C#の型名 | 特定のC#型を除外 |
IncludeDefaultCs |
string | C#デフォルト値 | 特定のデフォルト値を含む |
ExcludeDefaultCs |
string | C#デフォルト値 | 特定のデフォルト値を除外 |
ブール型フィルタプロパティ
テーブルやカラムの属性に基づいてフィルタリングするためのブール型プロパティも多数あります。
| プロパティ | 条件を満たす場合に生成 |
|---|---|
Pk / NotPk
|
主キーである / 主キーでない |
Identity / NotIdentity
|
IDENTITY列である / でない |
Unique / NotUnique
|
ユニーク制約がある / ない |
Session |
セッション保存対象のカラム |
Form |
フォーム表示対象のカラム |
Select |
SELECT対象のカラム |
Update |
UPDATE対象のカラム |
Calc / NotCalc
|
計算式カラムである / でない |
Join / NotJoin
|
JOIN対象のカラムである / でない |
History |
履歴対象のカラム |
ItemOnly / NotItem
|
Itemテーブルのみ / Item以外 |
GenericUi / NotGenericUi
|
汎用UIテーブルのみ / それ以外 |
HasIdentity / HasNotIdentity
|
テーブルにIDENTITY列がある / ない |
Class / NotClass
|
クラス型カラムである / でない |
これらのフィルタは、1つの定義に複数組み合わせることができ、すべての条件を満たすテーブル/カラムに対してのみコードが生成されます(AND条件)。
RepeatType:繰り返し展開の種類
テンプレートがどの単位で繰り返し展開されるかを RepeatType で指定します。
| RepeatType | 説明 | 繰り返し対象 |
|---|---|---|
| (空) | 繰り返しなし | 1回だけ展開 |
Table |
テーブル単位 | 各テーブル(Depts, Users, Issues等) |
Column |
カラム単位 | 各テーブルの各カラム |
BaseModel |
ベースモデルのカラム | 全テーブル共通のカラム |
BaseItemModel |
ベースItemモデルのカラム | Itemテーブル共通のカラム |
Join |
JOIN情報単位 | JOINカラム定義 |
Form |
フォーム単位 | フォーム定義 |
Display |
表示文字列単位 | 多言語表示文字列 |
DefinitionFile |
定義ファイル単位 | Code, Column, Template等のファイル |
DefinitionRow |
定義行単位 | 定義ファイル内の各行 |
DefinitionColumn |
定義カラム単位 | 定義ファイルの各カラム |
RepeatType の詳細は第5回で解説します。
SavedMemory / RestoreBySavedMemory パターン
CodeDefinition クラスには、各プロパティに対して Saved プレフィックスのついたプロパティが用意されています。
public string Id; public string SavedId;
public string Body; public string SavedBody;
public bool Pk; public bool SavedPk;
// ...以下、全プロパティに対して同様のペア
なぜ SavedMemory が必要か
コード生成エンジンでは、テンプレートの入れ子展開時に SetCodeDefinitionOption() メソッドでプロパティが一時的に書き換えられることがあります。
// Creators.Create() 内
Def.SetCodeDefinitionOption(placeholder, codeChildDefinition);
// ... コード生成処理 ...
codeChildDefinition.RestoreBySavedMemory(); // 元の値に復元
入れ子の展開が終わったあとに元の値に戻す必要があるため、初期化時にすべてのプロパティの値を Saved* に保存しておき、RestoreBySavedMemory() で一括復元します。
public void RestoreBySavedMemory()
{
Id = SavedId;
Body = SavedBody;
OutputPath = SavedOutputPath;
MergeToExisting = SavedMergeToExisting;
Source = SavedSource;
RepeatType = SavedRepeatType;
Indent = SavedIndent;
Separator = SavedSeparator;
Pk = SavedPk;
NotPk = SavedNotPk;
Identity = SavedIdentity;
// ... 全プロパティを復元
}
これは一種の Memento パターン の実装です。
Definition_Column:カラム定義
Definition_Column/ ディレクトリには、各テーブルの各カラムを定義するJSONファイルが格納されています。
カラム定義の例
{
"Id": "Users_UserId",
"ModelName": "User",
"TableName": "Users",
"Label": "ユーザ",
"ColumnName": "UserId",
"LabelText": "ユーザID",
"No": "1",
"TypeName": "int",
"Pk": "1",
"Identity": "1",
"Seed": "1",
"ControlType": "Id"
}
主要なカラム定義プロパティ
| プロパティ | 説明 | 例 |
|---|---|---|
ModelName |
モデル名 | "User" |
TableName |
テーブル名 | "Users" |
ColumnName |
カラム名 | "UserId" |
TypeName |
DB上のデータ型 |
"int", "nvarchar"
|
TypeCs |
C#上のデータ型 |
"Title", "Status" 等 |
Pk |
主キーの順序(0=主キーでない) | "1" |
Identity |
IDENTITY列か | "1" |
MaxLength |
最大長 | "100" |
Default |
デフォルト値 | "" |
DefaultCs |
C#でのデフォルト値 | "string.Empty" |
RecordingData |
記録時のデータ変換 | ".ToJson()" |
ByForm |
フォーム入力時の変換 | カスタム変換式 |
ByApi |
API入力時の変換 | カスタム変換式 |
ByDataRow |
DataRow読込時の変換 | カスタム変換式 |
カラム定義は、コード生成時にプレースホルダー置換の値を提供するだけでなく、フィルタ条件の判定にも使われます。たとえば Pk が 0 のカラムは、codeDefinition.Pk == true のフィルタに引っかからず、コード生成の対象から除外されます。
具体例:定義からコード生成までの流れ
Base_Property 定義を例に、定義からコード生成までの流れを追ってみましょう。
1. 定義ファイルの読み込み
{
"Id": "Base_Property",
"Indent": "2",
"Separator": "\\r\\n",
"NotCalc": "1",
"Exclude": "SiteSettings"
}
public #Type# #ColumnName# { get; set; }
2. CodeDefinitionの生成
上記の定義から以下のプロパティを持つ CodeDefinition が生成されます。
-
Id="Base_Property" -
Body="public #Type# #ColumnName# { get; set; }" -
Indent=2 -
Separator="\r\n" -
NotCalc=true -
Exclude="SiteSettings"
3. テンプレート展開
ベースモデルの各カラムに対して繰り返し展開されます。たとえば UpdatedTime カラムの場合:
-
#Type#→DateTime -
#ColumnName#→UpdatedTime
結果:public DateTime UpdatedTime { get; set; }(実際のコードにはインデント2段分が付与されます)
4. フィルタリング
-
NotCalc=trueなので、計算式カラムはスキップされる -
Exclude="SiteSettings"なので、SiteSettingsカラムはスキップされる
まとめ
第2回では、CodeDefinerのコード自動生成における入力データの構造を見てきました。
- 定義ファイルは JSONファイル(メタ情報)と Body.txtファイル(テンプレート本体)のペアで構成される
-
CodeDefinitionクラスは多数のフィルタプロパティを持ち、テーブルやカラムの特性に応じたコード生成を制御する -
RepeatTypeによってテンプレートの繰り返し単位が決まる - SavedMemory パターン により、入れ子展開中のプロパティ書き換えを安全に管理する
-
Definition_Column/のカラム定義がプレースホルダー置換の値とフィルタ条件の両方を提供する
次回は、これらの定義ファイルを受け取って実際にコードを展開するテンプレート展開エンジン(Creators.cs)の詳細を解説します。