0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

はじめに

前回は 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ファイルには、テンプレートのメタ情報(出力先パス、繰り返しタイプ、フィルタ条件など)が記述されています。

Definition_Code/Base.json
{
    "Id": "Base",
    "OutputPath": "Models\\Shared\\_BaseModel.cs",
    "Source": "Mvc"
}
Definition_Code/Base_Property.json
{
    "Id": "Base_Property",
    "Indent": "2",
    "Separator": "\\r\\n",
    "NotCalc": "1",
    "Exclude": "SiteSettings"
}

Body.txt ファイル(テンプレート本体)

_Body.txt で終わるファイルには、実際のC#コードテンプレートが記述されています。テンプレート中に <!--子定義ID--> 形式のプレースホルダーや #置換名# 形式の変数プレースホルダーが含まれます。

Definition_Code/Model_ReloadPermissions_Body.txt
if (ss.PermissionForCreating != null)
{
    ss.SetPermissions(
        context: context,
        referenceId: #ModelName#Id);
}

ペアリングの規則

JSONファイルの Id と Body.txt ファイル名の対応は次のとおりです。

JSONファイル Body.txtファイル
Base.jsonId: "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ファイルが格納されています。

カラム定義の例

Definition_Column/Users_UserId.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読込時の変換 カスタム変換式

カラム定義は、コード生成時にプレースホルダー置換の値を提供するだけでなく、フィルタ条件の判定にも使われます。たとえば Pk0 のカラムは、codeDefinition.Pk == true のフィルタに引っかからず、コード生成の対象から除外されます。

具体例:定義からコード生成までの流れ

Base_Property 定義を例に、定義からコード生成までの流れを追ってみましょう。

1. 定義ファイルの読み込み

Definition_Code/Base_Property.json
{
    "Id": "Base_Property",
    "Indent": "2",
    "Separator": "\\r\\n",
    "NotCalc": "1",
    "Exclude": "SiteSettings"
}
Definition_Code/Base_Property_Body.txt
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)の詳細を解説します。

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?