Skip to content

CodeDefiner(テーブル作成とコード自動生成) ​

第4版作成 最終更新 (日本時間)
確認バージョン1.5.8.1

プリザンターの Models ディレクトリなどにある大量の .cs ファイルは、CodeDefiner が定義ファイル(JSON)とテンプレート(_Body.txt)から自動生成したものです。このページでは、CodeDefiner のコマンド体系、定義ファイルの構造、テンプレート展開エンジン、コードマージ、RepeatType 別ハンドラ、プレースホルダー置換と型変換、そして複数 RDBMS 向けのデータベースの作成・移行とパラメータの引き継ぎ(merge)の仕組みをまとめます。

基本の仕組みは テンプレート + プレースホルダー置換 + 再帰展開 の組み合わせです。

INFO

ソースへのリンクは Implem/Implem.Pleasanter のコミット固定のパーマリンクです。コード自動生成部分はコミット 203cac8 を参照しています。コマンド・オプション、データベースの作成・更新、merge の節は 1.5.8.1(コミット fdcbb3f)です。

全体像 ​

CodeDefiner の役割 ​

CodeDefiner は、プリザンターのソリューションに含まれるコマンドラインツールで、主に次の役割を持ちます。

  1. データベースの構成(テーブル作成・マイグレーション)
  2. C# コードの自動生成(Model、Utility、Rds など)
  3. 定義アクセサコードの生成(Def.cs 内の定義クラス)
  4. ソリューションのバックアップ

ソリューション内の位置づけ ​

図を読み込み中…

CodeDefiner は Implem.DefinitionAccessor を通じて定義ファイルを読み込み、その定義に基づいて Implem.Pleasanter のソースコードを生成・更新します。

処理の流れ ​

図を読み込み中…

処理部品の関係(入力 → 処理 → 出力)は次のとおりです。

図を読み込み中…

エントリポイントとコマンド体系 ​

起動は Starter.Main() から始まります。最初の引数をアクション名として取得し、/ で始まるオプション引数をハッシュテーブルに格納します。

Implem.CodeDefiner/Starter.cs#L28-L66

csharp
ValidateArgs(args);
var argHash = ArgsType(args);
var action = args[0];

コマンド ​

コマンドDB 構成定義コード生成MVC コード生成説明
_rds✅--DB 構成のみ
rds✅✅✅DB 構成+全コード生成
_def-✅-定義コードのみ
def-✅✅定義コード+MVC コード
mvc--✅MVC コードのみ
splits_rds---Rds.cs のファイル分割
backup---ソリューションのバックアップ
migrate✅--別の DBMS からのデータ移行(DB 構成のあと Migration.json の移行元から移す)
ConvertTime---全テーブルの日時列を指定の時差だけずらす(/h で時差を指定。既定 -9)
merge---バージョンアップ時のパラメータの引き継ぎ(後述)
trial✅--トライアル・コミュニティ版向けの DB 構成

1.5.8.1 のソースで確認した一覧です(Starter.cs#L67-L127)。_rds に /c を付けると、DB を変更せずにマイグレーションの要否だけを確認します(後述の「確認だけ行う(/c)」)。

ConvertTime は _Bases などを除く全テーブル(_deleted / _history を含む)の datetime 型の列と、コメントの日時をずらします。/h は 9、-5:30、+9:00 のような「時」または「時:分」の形式で、それ以外は Invalid time format のエラーになります(TimeConverter.cs#L14-L74)。DB の日時はサーバーのタイムゾーンで入っているので(タイムゾーンの考え方)、サーバーのタイムゾーンを変えるときに使うコマンドです。

コード自動生成に関係するのは rds、_def、def、mvc の 4 つです。

オプション ​

オプション引数説明
/pパスサービスパス(Implem.Pleasanter のディレクトリ)
/tターゲット特定の定義 ID のみを生成(mvc / def / rds)
/l言語既定の言語。有効な値なら Service.json の DefaultLanguage に書き込む
/zタイムゾーン既定のタイムゾーン。OS のタイムゾーン ID(例: Tokyo Standard Time)で指定し、Service.json の TimeZoneDefault に書き込む。存在しない ID は InvalidTimeZoneException
/s-SA のパスワードをコンソールで入力し、Rds.json の UID=sa;PWD=...; の部分に書き込む
/r-/s と一緒に指定すると、Owner / User のパスワードをランダムな GUID に書き換える
/f-Issues / Results の列が定義より多い(列が減る)ときの中止を無視して続ける
/y-確認の入力(y)を省略する
/c-_rds で、DB を変更せずにマイグレーションの要否だけを確認する
/h時差ConvertTime でずらす時差
/b / /iパスmerge のバックアップ先 / インストール先
/e-trial で拡張列のファイルを使う

/p、/b、/i は次の引数をそのまま値として受け取ります(/p C:\web\pleasanter\Implem.Pleasanter のように空白で区切る)。ほかのオプションも、次の引数が / で始まらなければ値として受け取ります。同じ 2 文字のオプションを 2 回書くとエラーになります(Starter.cs#L212-L238、Starter.cs#L383-L396、Initializer.cs#L68-L91、Initializer.cs#L1280-L1331)。

/t は、開発時に特定のコード定義だけを再生成したい場合に便利です。

初期化 ​

コード生成の前に Initializer.Initialize() で定義ファイルが読み込まれます。このメソッドはプリザンター本体からも呼ばれる共通の初期化処理で、codeDefiner: true を渡すと CodeDefiner 固有の初期化が有効になります。

csharp
Initializer.Initialize(
    path,
    assemblyVersion: Assembly.GetExecutingAssembly().GetName().Version.ToString(),
    setLanguage: argHash.Get("l"),
    setTimeZone: argHash.Get("z"),
    codeDefiner: true,
    setSaPassword: argHash.ContainsKey("s"),
    setRandomPassword: argHash.ContainsKey("r"));

中心となるのは SetDefinitions() です。

csharp
public static void SetDefinitions()
{
    Displays.DisplayHash = DisplayHash();
    Def.SetCodeDefinition();      // ← コード定義の読み込み
    Def.SetColumnDefinition();    // ← カラム定義の読み込み
    Def.SetTemplateDefinition();
    Def.SetViewModeDefinition();
    Def.SetDemoDefinition();
    Def.SetSqlDefinition();
    SetDisplayAccessor();
    SetColumnDefinitionAccessControl();
}
定義名ディレクトリ用途
CodeDefinition_Code/C# コードのテンプレートと生成ルール
ColumnDefinition_Column/テーブルカラムの定義
TemplateDefinition_Template/サイトテンプレート
ViewModeDefinition_ViewMode/ビューモード定義
DemoDefinition_Demo/デモデータ定義
SqlDefinition_Sql/SQL テンプレート

コード自動生成で特に重要なのは Code と Column です。

生成の 3 フェーズ ​

コマンドに応じて、次の 3 フェーズが順に実行されます。

図を読み込み中…

  1. DB 構成(ConfigureDatabase): テーブル定義に基づいてスキーマを作成・更新します(後述の「テーブル作成の仕組み」を参照)。
  2. 定義コード生成(DefinitionAccessorCreator): 定義ファイルにアクセスするための C# コードを生成します。
  3. MVC コード生成(MvcCreator): Model・Utility・Rds などの C# コードを生成します。

DefinitionAccessorCreator ​

DefinitionAccessorCreator.cs#L9-L25

Source が "Def" の定義だけを抽出し、テンプレートを展開してからマージします。

csharp
private static void CreateCode()
{
    Def.CodeDefinitionCollection
        .OrderBy(o => o.Id)
        .Where(o => o.Source == "Def")    // Source が "Def" のものだけ
        .ForEach(codeDefinition =>
    {
        var code = Creators.Create(codeDefinition, new DataContainer("DefinitionFile"));
        if (!code.IsNullOrEmpty())
        {
            Merger.Merge(codeDefinition.OutputPath, code, codeDefinition.MergeToExisting);
        }
    });
}

MvcCreator ​

MvcCreator.cs#L10-L14

csharp
internal static void Create(string target)
{
    CreateEachTable(target);    // テーブルごとに繰り返し生成
    CreateNotRepeat(target);    // 繰り返しなしの生成
}
  • CreateEachTable: 全テーブル名(Depts、Groups、Users、Sites、Issues、Results、Wikis など)をループし、Source == "Mvc" かつ RepeatType == "Table" のコード定義を各テーブルに適用します。/t で指定したターゲットがあれば、その Id の定義だけに絞り込みます。
  • CreateNotRepeat: RepeatType が空の定義(テーブルに依存しない共通コード)を 1 回だけ生成します。
csharp
private static void CreateEachTable(string target)
{
    Def.TableNameCollection().ForEach(tableName =>
        Def.CodeDefinitionCollection
            .Where(o => target.IsNullOrEmpty() || o.Id == target)
            .Where(o => o.Source == "Mvc")        // Source が "Mvc" のもの
            .Where(o => o.RepeatType == "Table")  // RepeatType が "Table"
            .Where(o => !Table.CheckExclude(o, tableName))
            .ForEach(codeDefinition =>
            {
                var dataContainer = new DataContainer("Table");
                var modelName = Def.ModelNameByTableName(tableName);
                dataContainer.TableName = tableName;
                dataContainer.ModelName = modelName;
                var code = ReplacePlaceholder(
                    Creators.Create(codeDefinition, dataContainer),
                    tableName, modelName);
                var fileName = ReplacePlaceholder(
                    Directories.Outputs(codeDefinition.OutputPath),
                    tableName, modelName);
                Merger.Merge(fileName, code, codeDefinition.MergeToExisting);
            }));
}

DataContainer:コンテキストの受け渡し ​

生成中のテーブル名・カラム名などのコンテキストは DataContainer で各部品に引き回され、プレースホルダー置換の情報源になります。

DataContainer.cs#L7-L22

プロパティ説明
XlsIoCollection定義ファイル(Excel/JSON)のコレクション
Typeコンテナの種類("Table" や "DefinitionFile")
DefinitionName処理中の定義ファイル名("Code"、"Column" など)
ModelNameモデル名("User"、"Issue" など)
TableNameテーブル名("Users"、"Issues" など)
FormNameフォーム名
ColumnNameカラム名

定義ファイル ​

格納場所 ​

text
Implem.Pleasanter/App_Data/Definitions/
├── Definition_Code/      ← コード生成テンプレートと設定
│   ├── *.json            ← 定義の属性(約876ファイル)
│   └── *_Body.txt        ← テンプレート本体(約875ファイル)
└── Definition_Column/    ← カラム定義(約460ファイル)
    └── *.json

JSON と Body.txt のペア ​

Definition_Code/ には、1 つのコード定義につき最大 2 つのファイルがペアで存在します。

  • JSON ファイル: 出力先パス、繰り返しタイプ、フィルタ条件などのメタ情報
  • _Body.txt ファイル: C# コードのテンプレート本体。<!--子定義ID--> 形式と #置換名# 形式のプレースホルダーを含みます
json
{
    "Id": "Base",
    "OutputPath": "Models\\Shared\\_BaseModel.cs",
    "Source": "Mvc"
}
json
{
    "Id": "Base_Property",
    "Indent": "2",
    "Separator": "\\r\\n",
    "NotCalc": "1",
    "Exclude": "SiteSettings"
}
text
if (ss.PermissionForCreating != null)
{
    ss.SetPermissions(
        context: context,
        referenceId: #ModelName#Id);
}

JSON の Id とファイル名の対応は、Base.json(Id: "Base")→ Base_Body.txt、Model_ReloadPermissions.json → Model_ReloadPermissions_Body.txt のようになります。Body.txt が存在しない定義もあり、その場合は JSON 内の Body プロパティに直接テンプレートを書くか、子定義を組み合わせた展開のみを行います。

読み込み(XlsIo) ​

定義ファイルの読み込みは XlsIo クラスが担当します。歴史的に Excel ファイル(.xlsm)から読み込んでいた名残でこの名前ですが、現在は JSON ファイルベースの読み込みが主流です。

Implem.Libraries/Classes/XlsIo.cs#L9-L50

図を読み込み中…

*_Body.txt は、ファイル名から定義 ID を逆引きし、対応する行の Body 列に設定されます。これにより、メタ情報は JSON、テンプレート本体はテキストファイルで管理できます。

CodeDefinition のプロパティ ​

基本プロパティ:

プロパティ型説明例
Idstring定義の一意識別子"Base", "Model_Property"
Bodystringテンプレート本体C# コードテンプレート
OutputPathstring出力ファイルのパス"Models\\#ModelName#\\..."
Sourcestring定義の種別"Mvc", "Def"
RepeatTypestring繰り返しの種類"Table", "Column" など
MergeToExistingbool既存コードにマージするかtrue / false

テンプレート制御プロパティ:

プロパティ型説明
Indentintテンプレートのインデント深度
Separatorstring繰り返し展開時の区切り文字(例:"\\r\\n")
Orderstringカラムの並び順を指定するフィールド名
NoSpacebool空行を除去するか
ReplaceOldstring展開後に置換する旧文字列
ReplaceNewstring展開後に置換する新文字列

文字列フィルタ(カンマ区切り):

プロパティフィルタ対象説明
Include / Excludeテーブル名 / カラム名包含 / 除外指定
IncludeTypeName / ExcludeTypeNameDB の型名特定の型名だけに適用 / 除外
IncludeTypeCs / ExcludeTypeCsC# の型名特定の C# 型だけに適用 / 除外
IncludeDefaultCs / ExcludeDefaultCsC# デフォルト値特定のデフォルト値を含む / 除外

ブール型フィルタ:

プロパティ条件を満たす場合に生成
Pk / NotPk主キーである / 主キーでない
Identity / NotIdentityIDENTITY 列である / でない
Unique / NotUniqueユニーク制約がある / ない
Sessionセッション保存対象のカラム
Formフォーム表示対象のカラム
SelectSELECT 対象のカラム
UpdateUPDATE 対象のカラム
Calc / NotCalc計算式カラムである / でない
Join / NotJoinJOIN 対象のカラムである / でない
History履歴対象のカラム
ItemOnly / NotItemItem テーブルのみ / Item 以外
GenericUi / NotGenericUi汎用 UI テーブルのみ / それ以外
HasIdentity / HasNotIdentityテーブルに IDENTITY 列がある / ない
Class / NotClassクラス型カラムである / でない

フィルタは 1 つの定義に複数組み合わせることができ、すべての条件を満たすテーブル / カラムに対してのみコードが生成されます(AND 条件)。

SavedMemory / RestoreBySavedMemory(Memento パターン) ​

CodeDefinition の各プロパティには Saved プレフィックス付きのペア(Id と SavedId、Body と SavedBody など)があります。入れ子展開時に SetCodeDefinitionOption() でプロパティが一時的に書き換えられるため、初期化時に全プロパティを Saved* に保存しておき、展開後に RestoreBySavedMemory() で一括復元します。

csharp
// Creators.Create() 内
Def.SetCodeDefinitionOption(placeholder, codeChildDefinition);
// ... コード生成処理 ...
codeChildDefinition.RestoreBySavedMemory();  // 元の値に復元

カラム定義(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"
TypeNameDB 上のデータ型"int", "nvarchar"
TypeCsC# 上のデータ型"Title", "Status" など
Pk主キーの順序(0 = 主キーでない)"1"
IdentityIDENTITY 列か"1"
MaxLength最大長"100"
Defaultデフォルト値""
DefaultCsC# でのデフォルト値"string.Empty"
RecordingData記録時のデータ変換".ToJson()"
ByFormフォーム入力時の変換カスタム変換式
ByApiAPI 入力時の変換カスタム変換式
ByDataRowDataRow 読込時の変換カスタム変換式

カラム定義は、プレースホルダー置換の値を提供するだけでなく、フィルタ条件の判定にも使われます。たとえば Pk が 0 のカラムは、Pk フィルタ付きの定義では生成対象から除外されます。

テンプレート展開エンジン(Creators.cs) ​

2 種類のプレースホルダー ​

Utilities/CodePatterns.cs#L3-L8

csharp
internal static class CodePatterns
{
    internal const string IdPlaceholder = "<!--.*?-->";
    internal const string Id = "[A-Za-z0-9_]+";
    internal const string ReplacementPlaceholder = "(?<=#)[^#]+?(?=#)";
}
パターン正規表現マッチ例用途
IdPlaceholder<!--.*?--><!--Model_Property-->子定義の埋め込み位置を検出
Id[A-Za-z0-9_]+Model_PropertyIdPlaceholder 内から定義 ID を抽出
ReplacementPlaceholder(?<=#)[^#]+?(?=#)#ModelName# 中の ModelName値置換プレースホルダーを検出
  • <!--ID-->: 他の CodeDefinition を参照して展開する構造的プレースホルダー
  • #Name#: テーブル名やカラム名などの値で直接置換する値プレースホルダー

Creators.Create() の処理 ​

Creators.cs#L12-L97

図を読み込み中…

csharp
internal static string Create(CodeDefinition codeDefinition, DataContainer dataContainer)
{
    var code = codeDefinition.FormattedCode();
    foreach (var placeholder in code.RegexValues(CodePatterns.IdPlaceholder))
    {
        var id = placeholder.RegexFirst(CodePatterns.Id);
        var codeChildDefinition = Def.CodeDefinitionCollection
            .FirstOrDefault(o => o.Id == id);
        if (codeChildDefinition != null)
        {
            Def.SetCodeDefinitionOption(placeholder, codeChildDefinition);
            var codeChildCollection = new List<string>();
            switch (codeChildDefinition.RepeatType)
            {
                case "Table":
                    Table.SetCodeCollection(
                        codeChildDefinition, codeChildCollection, dataContainer);
                    break;
                case "Column":
                    Column.SetCodeCollection(
                        codeChildDefinition, codeChildCollection, dataContainer);
                    break;
                // ... 他のRepeatType ...
            }
            code = CodeChildCollection(
                code, placeholder, codeChildDefinition, codeChildCollection);
            codeChildDefinition.RestoreBySavedMemory();
        }
    }
    ReplaceCode(ref code, dataContainer.TableName);
    return code;
}

FormattedCode():インデント付与 ​

Creators.cs#L141-L167

単一行なら Indent 分のタブ + Body、複数行なら各行にインデントを付与します。空行はそのまま、<!-- で始まる行(子定義プレースホルダー)にはインデントを付けません。子定義は展開時に自身のインデントを適用するため、二重インデントを防いでいます。最後に NoSpace に応じて空行を除去します。

再帰展開 ​

子定義のテンプレートにさらに孫定義のプレースホルダーがあれば、Creators.SetCodeCollection() を中継して Creators.Create() が再帰的に呼ばれます。

図を読み込み中…

csharp
internal static void SetCodeCollection(
    ref string code,
    List<string> codeCollection,
    CodeDefinition codeDefinition,
    DataContainer dataContainer,
    Action replaceCode)
{
    code = Create(codeDefinition, dataContainer);  // ← 再帰呼び出し
    replaceCode();                                  // 値プレースホルダーの置換
    codeCollection.Add(code);                       // 結果をコレクションに追加
}

インラインオプション(SetCodeDefinitionOption) ​

構造的プレースホルダーには括弧でオプションを埋め込めます。SetCodeDefinitionOption() が括弧内をカンマ・= で分解し、子定義のプロパティ(Indent、NotPk、Include など)を一時的に上書きします。同じ子定義を異なる条件で再利用できます。

text
<!--Model_Property(Indent=3, NotPk=1)-->

<!-- 主キーカラムだけを対象にして展開 -->
<!--Model_Property(Pk=1)-->

<!-- 主キーでないカラムだけを対象にして展開 -->
<!--Model_Property(NotPk=1)-->

展開結果の結合 ​

各ハンドラで生成されたコード片は Separator で結合されます("\\r\\n" は実際の改行に変換)。

Separator の値実際の区切り用途の例
\\r\\n改行プロパティ宣言の羅列
,\\r\\nカンマ+改行enum の値やメソッド引数
(空)空文字列コード片を直接結合

展開の具体例 ​

親定義 Model(Source: "Mvc"、RepeatType: "Table")のテンプレートと、子定義 Model_Property(Indent: 2、Separator: "\\r\\n")が次のとおりだとします。

text
namespace #ServiceName#.Models
{
    public class #ModelName#Model : _BaseModel
    {
<!--Model_Property-->
    }
}
text
public #Type# #ColumnName# { get; set; }

Users テーブルの場合、<!--Model_Property--> が Column.SetCodeCollection() で Users の各カラムに展開・結合され、最後に MvcCreator.ReplacePlaceholder() で #ServiceName# → Implem.Pleasanter、#ModelName# → User に置換されます。

csharp
namespace Implem.Pleasanter.Models
{
    public class UserModel : _BaseModel
    {
        public int UserId { get; set; }
        public string LoginId { get; set; }
        public string Name { get; set; }
        // ...
    }
}

RepeatType 別のハンドラ(Parts) ​

対応表 ​

RepeatType繰り返し対象ハンドラ(Parts/)
(空)繰り返しなし(1 回だけ展開)-
Table各テーブル(Depts, Users, Issues など)Table.cs
Column各テーブルの各カラムColumn.cs
BaseModel全テーブル共通のカラムBaseModel.cs
BaseItemModelItem テーブル共通のカラムBaseItemModel.cs
JoinJOIN カラム定義Join.cs
Formフォーム定義Form.cs
Display多言語表示文字列Display.cs
DefinitionFile定義ファイル(Code, Column, Template など)DefinitionFile.cs
DefinitionRow定義ファイル内の各行DefinitionRow.cs
DefinitionColumn定義ファイルの各カラムDefinitionColumn.cs

すべてのハンドラは 繰り返し対象の取得 → フィルタ → Creators.SetCodeCollection() で再帰展開 → 値置換 → codeCollection に追加 という共通パターンです。

ネストの関係は次のとおりです。

図を読み込み中…

Table ​

Parts/Table.cs#L10-L34

Def.TableNameCollection(order: codeDefinition.Order) をループし、DataContainer の TableName / ModelName を切り替えながら展開します。終了後は親の値に戻します。

テーブルレベルのフィルタは Table.CheckExclude() にあり、すべて除外判定(true で対象外)です。

Parts/Table.cs#L36-L53

フィルタ対象となる条件対象テーブルの例
ItemOnlyItemId > 0 のカラムを持つテーブルのみIssues, Results, Wikis
NotItemItemId > 0 のカラムを持たないテーブルのみDepts, Groups, Users
GenericUiGenericUi カラムを持つテーブルのみIssues, Results
HasIdentityIDENTITY 列を持つテーブルのみほぼすべて
HasTableNameIdテーブル名 + Id のカラムがあるテーブルのみUsers(UserId), Issues(IssueId)
Exclude指定テーブルを除外定義で指定
Include指定テーブルのみ対象定義で指定

Table.ReplaceCode() では大文字・小文字の 6 種類のプレースホルダーを置換し、最後に ReplaceOld / ReplaceNew を適用します。

プレースホルダー変換例(Issues テーブル)
#ModelName#そのままIssue
#modelName#先頭を小文字issue
#modelname#すべて小文字issue
#TableName#そのままIssues
#tableName#先頭を小文字issues
#tablename#すべて小文字issues

Column ​

Parts/Column.cs#L10-L37

現在のテーブルのカラムを CheckExclude で絞り込み、Order(未指定なら No)で並べて展開します。Table ハンドラの内側で呼ばれることが多く、テーブル × カラムの 2 重ループになります。

Column.CheckExclude() は CodeDefiner で最も多くのフィルタ条件を持つメソッドです。

分類フィルタ
型IncludeTypeName、ExcludeTypeName、IncludeTypeCs、ExcludeTypeCs
キーPk / NotPk、Identity / NotIdentity、Unique / NotUnique、IdentityOrPk
UIForm、Select、Update、GridColumn、FilterColumn、EditorColumn、TitleColumn
その他Calc / NotCalc、Join / NotJoin、History / PkHistory、Class / NotClass、Null / NotNull、Session、NotBase

NotBase はベースモデル(全テーブル共通)に含まれるカラムを除外するフィルタです。Item モデルなら BaseItem カラム、それ以外なら Base カラムに同名カラムがあれば除外しますが、EachModel フラグが立っているカラムは除外しません。

BaseModel / BaseItemModel ​

全テーブル共通(BaseModel)/ Item テーブル共通(BaseItemModel)のカラムのコードを生成します。どちらも EachModel = true のカラムを除外します。EachModel は、ベースカラムでありながらテーブルごとに異なる実装が必要なカラムを、各テーブル固有のコードとして生成するためのフラグです。

Join ​

Parts/Join.cs#L10-L38

JoinTableName が設定されているカラムを対象に繰り返します。

プレースホルダー説明
#JoinTableName#結合先テーブル名
#JoinType#結合タイプ(SqlJoin.JoinTypes.Inner など)
#JoinExpression#結合条件式
#TableNameAlias#テーブルエイリアス
#ColumnBracket#カラムのブラケット表記
#ColumnBrackets#計算列を含むカラムブラケット

#JoinType# はカラム定義の JoinType から変換されます(inner join → SqlJoin.JoinTypes.Inner、left outer join → SqlJoin.JoinTypes.LeftOuter、right outer join → SqlJoin.JoinTypes.RightOuter)。

Form ​

テーブル内のユニークな ModelName / FormName の組み合わせを繰り返します。#FormName# は、FormName が未設定なら ModelName + "Form" に展開されます。

Display ​

DisplayAccessor.Displays.DisplayHash の各表示文字列の各言語を繰り返します。多言語リソースのコード生成については 多言語対応の実装 も参照してください。

プレースホルダー説明
#DisplayId#表示 ID(言語サフィックス付き)
#DisplayContent#表示テキスト
#DisplayCssClass#CSS クラス名
#DisplayContentEncoded#HTML エンコード済みテキスト

特有のフィルタとして DisplayLanguages(多言語エントリを含めるか)と ClientScript(クライアントスクリプト用のエントリか)があります。

DefinitionFile / DefinitionRow / DefinitionColumn ​

定義ファイル自体のメタ情報をコード化するハンドラで、主に Def.cs(定義アクセサ)の生成に使われます。

ハンドラ繰り返し対象主なプレースホルダー
DefinitionFile定義ファイルの種類(Code, Column, Template, ViewMode, Demo, Sql)#File#、#file#、#ColumnNames#
DefinitionRow定義ファイル内の各行(ヘッダ行を除き、ID が空でない行)#Id#(ReservedWords.ValidName() で C# 識別子化)
DefinitionColumn定義ファイルの各カラム#DefColumnName#(エスケープ済み)、#DefColumnNameOriginal#、#Type#、#CastType#、#SetDefault#

プレースホルダー置換と型変換 ​

Column.ReplaceCode ​

Parts/Column.cs#L176-L269

プレースホルダー説明値の例
#ColumnName#カラム名(先頭大文字)UserId, LoginId
#columnName#カラム名(先頭小文字)userId, loginId
#Type#C# 型(TypeCs → TypeName の優先順)string, int, Title
#RecordingType#DB 記録時の型int, nvarchar
#RecordingData#記録時のデータ変換式.ToJson()
#CastType#キャスト式.ToInt(), .ToString()
#DefaultData#デフォルト値0, string.Empty
#InitialValue#初期値0, false, "[]"
#Hash#ハッシュ変換式(Hash 列の場合).Sha512Cng()
#MaxLength#最大長バリデーション.MaxLength(100)
#Calc#計算式テーブル名・モデル名を置換済みの式
#ColumnCount#対象カラムの総数15
#GridEnable#一覧表示の有効/無効1, 0

固定のもの以外に、カラム定義のプロパティ名がそのままプレースホルダーとして使えます(動的プレースホルダー)。Def.ColumnXls.XlsSheet.Columns に含まれる名前なら #プロパティ名# がそのカラムの値に置換されるため、カラム定義に新しいプロパティを追加するだけでテンプレートから使えます。

型変換プレースホルダー(Converts.cs) ​

データの入口に応じた型変換コードを生成します。

Converts.cs#L7-L192

プレースホルダー用途ソースメソッド
#ByForm#(value)フォーム入力からの変換Converts.ByForm()
#ByApi#(data.Property)API 入力からの変換Converts.ByApi()
#ByDataRow#(dataRow)DataRow からの変換Converts.ByDataRow()
#BySession#(session)セッションからの変換Converts.BySession()

たとえばテンプレート #ColumnName# = #ByForm#(value); は、Users の LoginId(nvarchar)なら LoginId = value.ToString();、UserId(int)なら UserId = value.ToInt(); に展開されます。

変換方法は次の優先順位で決まります。

図を読み込み中…

CastType() の対応:

DB 型名C# 型分類キャスト式
nvarchar, varchar, char などCsString.ToString()
intCsInt.ToInt()
bigintCsLong.ToLong()
decimal, money などCsDecimal.ToDecimal()
floatCsSingle.ToSingle()
realCsDouble.ToDouble()
datetimeCsDateTime.ToDateTime()
bitCsBool.ToBool()

TypeCs が設定されたカラムは、単純なキャストではなくオブジェクト生成になります。

TypeCs変換元生成されるコード
TitleByFormnew Title(value.ToString())
TimeByFormnew Time(context, value.ToDateTime(), byForm: true)
StatusByApinew Status(data.Status.ToInt())
CompletionTimeByDataRownew CompletionTime(context, dataRow, column.ColumnName)
その他-value as TypeCs ?? defaultValue

また、Issues と Results の Body カラムの ByForm には、BinaryUtilities.NormalizeFormBinaryPath(context, value.ToString()) によるバイナリパス正規化が入ります。

DefaultData / InitialValue ​

型分類デフォルト値
文字列string.Empty
文字列(RecordingData が .ToJson() / .RecordingJson())TypeCs が List<long> / Attachments なら "[]"、それ以外は "{}"
数値0
日時0.ToDateTime()
ブールfalse(Default="1" のとき true)
バイナリnull

カラム定義に DefaultCs があればそれが最優先、次に Default、どちらもなければ上表の値が使われます。

予約語エスケープと識別子化(ReservedWords.cs) ​

Implem.Libraries/Utilities/ReservedWords.cs#L4-L48

  • EscapeReservedWord(): C# の予約語(class、int など)とコンテキストキーワード(add、dynamic、get、set、value、var、where、yield など)に一致すると、既定でアンダースコアを前置します(class → _class)。
  • ValidName(): 定義 ID の特殊文字を C# 識別子として有効な文字列に変換します。DefinitionRow ハンドラで使われます。
元の文字変換後
空白_space_
._dot_
#_sharp_
,_comma_
:_colon_
> [ ] ( ) -_
+_plus_
=_equal_
^_caret_
"_yen_
*_asterisk_
@_atmark_

置換のレベルと実行順序 ​

図を読み込み中…

実行順序は次のとおりです(図のレベル番号と実行順序は一致しません)。

順序レベル対象実施箇所
1構造的プレースホルダー<!--子定義ID-->Creators.Create() で再帰展開
2ハンドラレベルの値置換Table: #ModelName#, #TableName# など / Column: #ColumnName#, #Type# など / Join: #JoinTableName# など / 型変換: #ByForm#() など / DefinitionColumn: #DefColumnName# など各ハンドラの ReplaceCode()
3Creators レベルの値置換#IdType#(ID 列の C# 型)、#IdTypeDefault#(ID 列のデフォルト値)、#CastIdType#(ID 列のキャスト式)Creators.ReplaceCode()
4MvcCreator レベルの置換#ServiceName#、#TableName#、#tableName#、#ModelName#、#modelName#MvcCreator.ReplacePlaceholder()

レベル 4 の置換は出力ファイルのパスにも適用されます。たとえば OutputPath が Models\#ModelName#\#ModelName#Model.cs なら Models\Issue\IssueModel.cs に展開されます。

コードマージ(Merger.cs / Parser.cs) ​

CodeDefiner は定義の変更のたびにコードを再生成しますが、生成コードへの手動修正(固有のビジネスロジック、クエリ最適化、例外処理など)が失われないよう、既存ファイルとマージします。

Merger.Merge() ​

Merger.cs#L9-L44

図を読み込み中…

マージ処理が行われるのは C# ファイルだけで、それ以外は単純に上書きされます。

MergeToExisting でマージの方向(ベースとなるコード)が決まります。

MergeToExistingベースマージされる側ユースケース
true既存コード新しい生成コード既存の手動修正を保持しつつ新機能を追加
false新しい生成コード既存コード生成コードを優先しつつ手動修正を取り込み

Parser:C# コードの構造解析 ​

Parser.cs#L7-L27

Parser は C# ソースを 名前空間 → クラス → メソッド の階層(CodeTypes: Namespace / Class / Method)に分解します。各ノードはシグネチャ(Id)、名前、XML ドキュメントコメント(Description)、Fixed / NotMerge マーカー、テキスト片(TextCollection)と子メンバー(MemberCollection)を持ち、子メンバーごとに Parser が再帰生成されます。

図を読み込み中…

たとえば、クラス UserModel にプロパティ UserId、/// Fixed: 付きメソッド CustomMethod()、自動生成メソッド GeneratedMethod() がある場合、次のように分解されます。

図を読み込み中…

/// Fixed: と /// NotMerge: ​

マーカー効果
/// Fixed:再生成時に既存の内容が保持される(新しい生成コードで上書きしない)
/// NotMerge:マージ処理から完全に除外される
csharp
/// Fixed:
public string CustomMethod()
{
    return "この内容は再生成時に保持される";
}

検出ロジックは次のとおりです。

csharp
private void SetFixed()
{
    Fixed = Description.IndexOf("/// Fixed:") != -1;
}

private void SetNotMerge()
{
    NotMerge = Description
        .RegexExists("^ {" + Indent + "}/// NotMerge:.*", RegexOptions.Multiline);
}

MergeCode() の判定 ​

Parser.cs#L207-L224

csharp
private void MergeCode(
    Parser baseCsParent, Parser baseCs, Parser margeCs, bool margeToExisting)
{
    // ステップ1: Fixed メンバーの処理
    if (margeCs.Fixed)
    {
        MergeFixedCode(baseCsParent, baseCs, margeCs);
    }

    // ステップ2: MergeToExisting フラグに基づくマージ
    if (margeToExisting)
    {
        MergeToExisting(baseCsParent, baseCs, margeCs);
    }

    // ステップ3: 子メンバーに対して再帰的にマージ
    margeCs.MemberCollection.Select(o => o.Value).ForEach(memberOld =>
        MergeCode(baseCs, SameMember(baseCs, memberOld), memberOld, margeToExisting));
}

図を読み込み中…

メンバーの同一性は Id(アクセス修飾子や引数リストを含むシグネチャ全体)で判定されるため、オーバーロードも区別されます。マージ後は Code() が TextCollection と MemberCollection をインデックス順に結合し、メンバーの出現順序を維持します。

マージの例(MergeToExisting = false) ​

既存ファイルに /// Fixed: 付きの ValidateCustomRule() があり、新しい生成コードで Name プロパティが追加・Update() が変更された場合、結果は次のようになります。

csharp
namespace Implem.Pleasanter.Models
{
    public class UserModel : _BaseModel
    {
        public int UserId { get; set; }
        public string LoginId { get; set; }
        public string Name { get; set; }     // 新カラムが反映

        /// Fixed:
        public bool ValidateCustomRule()
        {
            // 開発者が手動で追加したバリデーション(保持される!)
            return LoginId.Length >= 3;
        }

        public void Update()
        {
            // 自動生成された更新処理(最新版に更新)
        }
    }
}

CodeWriter:差分がある場合のみ書き込み ​

Utilities/CodeWriter.cs#L7-L21

既存コードと新コードが完全に同じなら書き込みをスキップしてコンソールに - を表示し、差分がある場合だけ書き込んで変更されたファイルパスを表示します。不要なタイムスタンプ更新や Git の差分ノイズを防ぐためです。

データベースの作成・更新(_rds / rds) ​

プリザンターは SQL Server、PostgreSQL、MySQL に対応しており、CodeDefiner は RDBMS ごとの違いを吸収しながらデータベース・ユーザー・テーブルを作成・更新します。以下は 1.5.8.1 のソースで確認した内容です。

処理の順序 ​

ConfigureDatabase() は、まず SA の接続文字列で DB に接続できるかを確かめ、Configurator.Configure() を呼びます(Starter.cs#L398-L436、Configurator.cs#L18-L48)。

図を読み込み中…

  • y の確認は 5 のあとに出ます。確認より前に 1〜4(セッション切断・DB とユーザーとスキーマの作成 / 更新)はもう実行されています。 確認で y 以外を入力して中止しても、1〜4 は取り消されません。入力は大文字小文字を区別せず、y と yes を受け付けます(Configurator.cs#L89-L139、Configurator.cs#L346-L350)。
  • 5 では Issues と Results について、DB にあって定義に無い列を探します。見つかると列名を表示し、/f が無ければ中止します。拡張列の数を減らした場合などに、列ごとデータを失うのを防ぐためのチェックです。
  • Provider が Azure のときは 1〜4 と 7 を飛ばします(Pleasanter Setup と Rds.json の Provider)。

接続は 2 種類を使い分けます。DB・ユーザーの作成と権限付与は SA の接続(Def.SqlIoBySa()、SaConnectionString)、テーブルの作成・移行は Owner の接続(Def.SqlIoByAdmin()、OwnerConnectionString)です。

段階SQL ServerPostgreSQLMySQL
DB の作成create database ... collate japanese_90_ci_as_ksCreateDatabase はダミー(select 1)で、CreateUserForPostgres / CreateDatabaseForPostgres で作成create database ... collate utf8mb4_general_ci
新規作成時のスキーマ-サービス名のスキーマを Owner の所有で作成し、pg_trgm・pgcrypto 拡張を入れる-
Owner の権限db_owner ロールalter role のみ(テーブルの所有者になるため)create, alter, index, drop と select, insert, update, delete, create routine, alter routine ... with grant option
User の権限db_datareader・db_datawriter ロールスキーマ内の Owner 所有の全テーブルに select, insert, update, deleteselect, insert, update, delete, create routine, alter routine

MySQL の権限付与は、MySqlConnectingHost にカンマ区切りで書いたホストごとに行います(PrivilegeConfigurator.cs#L11-L27)。

PostgreSQL で使うスキーマは次のように決まります。DB がまだ無ければサービス名のスキーマを作ります。DB がすでにあれば、サービス名のスキーマがあればそれを、無ければ public を使います(RdsConfigurator.cs#L79-L96、SchemaConfigurator.cs#L9-L36)。

対象のテーブル ​

管理するテーブルの一覧はコードに書かれておらず、Def.TableNameCollection() がカラム定義(Definition_Column/*.json)の TableName から導きます(_Base で始まるモデルは除く)。定義ファイルを足せばテーブルが増え、消せば管理対象から外れます(Def.cs#L106-L116)。

テーブルは削除されない

CodeDefiner にはテーブルを削除する処理がありません。定義から消したテーブルも、後述の移行で退避された旧テーブル(_Migrated_...)も DB に残ります。

1 つのテーブル定義から、最大 3 つの物理テーブルを作ります(TablesConfigurator.cs#L151-L214)。

物理テーブル作る条件列
{テーブル名}常に対象の列すべて
{テーブル名}_deletedHistory が 1 以上の列があるとき通常テーブルと同じ
{テーブル名}_history同上History が 1 以上の列だけ(History の順)

_deleted / _history の列には IDENTITY を付けません。対象の列からは、NotUpdate の列、JoinTableName のある列(結合用)、Calc のある列(計算列)、SysLogs の列のうち SchemaVersion が Rds.json の SysLogsSchemaVersion より大きいもの、テーブル固有の列がすべて ExcludeBaseColumns のときの共通列(_Bases / _BaseItems 由来)を除きます。

Quartz.NET のテーブル(Quartz.json の Clustering.TablePrefix で始まるもの。既定 QRTZ_)は、Clustering.Enabled が true のときだけ作ります。PostgreSQL では Quartz のテーブル名・列名を小文字にします(TablesConfigurator.cs#L15-L34)。

全テーブルのあと、全文検索のインデックスを作ります。SQL Server は ftx の全文カタログと Items・Binaries の全文インデックス、PostgreSQL は DB を新規作成したときだけ Items の FullText に gin_trgm_ops の GIN インデックス、MySQL は Items の FullText に ngram パーサーの全文インデックスです(TablesConfigurator.cs#L59-L149)。

作り直すかどうかの判定 ​

テーブルごとに、無ければ作成、あれば Tables.HasChanges() で定義と DB を比べ、違いがあれば移行(作り直し)します(TablesConfigurator.cs#L216-L267、Tables.cs#L165-L199)。次のどれか 1 つでも違えば移行します。

比較内容
列の数定義の列数と DB の列数
列ごと(定義の順に比較)列名、型(DB の型名を SQL Server の型名に戻して比較)、サイズ、NULL 許可、IDENTITY(_history / _deleted は比較しない)
既定値Default のある列を列名順に 列名,既定値 で並べた文字列の一致(_history の Ver は除く)
インデックス定義から作るインデックス名の一覧と DB のインデックス名の一覧(PostgreSQL は ftx を除く。MySQL は主キーの列構成とそれ以外のインデックス名を別々に比較)
  • 列はテーブル上の並び順で比べるので、同じ列でも順番が違えば移行になります(Columns.cs#L25-L95)。
  • サイズは char / varchar なら MaxLength と、nchar / nvarchar なら MaxLength × 係数(SQL Server 2、PostgreSQL・MySQL 4)と比べます。decimal は Size(18,4 など)の文字列、max(-1)どうしは同じとみなします(ColumnSize.cs)。
  • Rds.json の DisableIndexChangeDetection が true のときはインデックスを比べません。配布時の Rds.json は true なので、インデックスの違いだけでは移行しません(Indexes.cs#L253-L279、Rds.json#L11)。

移行(作り直し)のしかた ​

移行は ALTER TABLE ではなく、新しい構造のテーブルを作ってデータを入れ直し、名前を入れ替える方式です(Tables.cs#L41-L147)。

図を読み込み中…

IDENTITY 列のあるテーブル(_history / _deleted を除く)は、SQL Server では set identity_insert ... on/off で囲み、PostgreSQL では最後に setval(pg_get_serial_sequence(...), max(...)) で連番を合わせます。MySQL は通常と同じです(Sqls/SQLServer/MigrateTableWithIdentity.sql、Sqls/PostgreSQL/MigrateTableWithIdentity.sql)。

データを入れ直すときの列の対応は次のとおりです。

条件新テーブルに入れる値
旧テーブルに同じ名前の列があるその列の値
旧テーブルに無く、定義に OldColumnName があるOldColumnName の列の値(列名の変更)
旧テーブルに無く、Default が無く NOT NULL型に応じた値(文字列は ''、日時は現在日時、真偽は既定の真偽値、ほかは 0)
上記以外入れない(NULL または既定値になる)

移行は全件をコピーするので、データの多いテーブルでは時間がかかります。旧テーブルは _Migrated_ 付きの名前で残るため、DB の容量も増えます。

確認だけ行う(/c) ​

_rds /c は DB を変更しません。Local のときは DB が無いかを確認して DB 名・Owner / User のユーザー名・スキーマ名を表示し、DB が無ければそう表示して終わります。DB があれば全テーブルを比較します。テーブルごとの行に加えて、作成・移行が必要なテーブルには CreateTable / MigrateTable の行が出ますが、SQL は実行しません。変更が無ければそのことを表示します(Configurator.cs#L49-L86)。バージョンアップの前に、どのテーブルが作り直されるかを見るのに使えます。

画面とログへの出力 ​

CodeDefiner の出力は Consoles.Write() から Trace に書かれ、Trace にはログファイル logs/Implem.CodeDefiner_yyyyMMdd_HHmmss.log とコンソールの両方が登録されています。そのため、ログに出る行はコンソールにも同じく出ます(Starter.cs#L28-L35、Consoles.cs#L18-L45)。

text
<INFO> TablesConfigurator.ConfigureTableSet: Users
<INFO> Tables.MigrateTable: Users
<INFO> Tables.CreateTable: Users

<種類> クラス名.メソッド名: 内容 の形式です。テーブルの処理では、テーブルごとに ConfigureTableSet の行が出て、作成・移行するときは CreateTable / MigrateTable の行が続きます(移行は内部で新テーブルを作るので CreateTable の行も出ます)。1 行出たあと次の行が出るまでは、そのテーブルの処理(移行ならデータのコピー)が続いている状態です。データの多いテーブルの移行中は、画面が長く止まって見えます。

テーブル単位のエラーは <ERROR> の行として出て、次のテーブルに進みます。最後にエラーの件数とログファイルのパスが表示されます(エラーが無ければ完了のメッセージ)。DB に接続できないときなど abort のエラーは、その場で終了します。Pleasanter Setup から実行したときにログで進み具合を見る方法は Pleasanter Setup と Rds.json の Provider を参照してください。

SQL テンプレート ​

CodeDefiner が実行する SQL は、1.5.8.1 では App_Data/Definitions/Sqls/{SQLServer|PostgreSQL|MySQL}/*.sql(各 55 ファイル、同じファイル名)から読み込まれ、Def.Sql のフィールドに入ります。RDBMS は Rds.json の Dbms で選ばれます(Def.cs#L5961-L5985、Directories.cs#L63-L66)。#TableName# などのプレースホルダーを Replace() で置き換えて実行します。

テーブル作成では、CreateTable.sql に対して次の置換を行います(Tables.cs#L13-L39)。

プレースホルダー置き換えられる内容作るメソッド
#Columns#列の定義(型、サイズ、IDENTITY、NULL 制約)CreateColumn()
#Pks#主キー制約CreatePk()
(主キーの後ろ)インデックス定義CreateIx()
#Defaults#(SQL Server / PostgreSQL)/ #ModifyColumn#(MySQL)既定値CreateDefault()
#DropConstraint#既存の制約の削除DropConstraint()
#TableName#テーブル名(移行時は一時テーブル名)-

ファクトリによる RDBMS 切り替え ​

RDBMS 固有の処理は、RdsFactory.Create(Parameters.Rds.Dbms) が返す ISqlObjectFactory(SqlServerObjectFactory / PostgreSqlObjectFactory / MySqlObjectFactory)で切り替えます。SQL の方言は ISqls、型の変換は ISqlDataType、RDBMS ごとの設定値は ISqlDefinitionSetting です。SQL テンプレートで吸収できない違い(列の作成、MySQL のインデックスと既定値、サイズの比較)は、C# の switch (Parameters.Rds.Dbms) で分けています。

Implem.CodeDefiner/Starter.cs#L66

項目プロパティSQL ServerPostgreSQLMySQL
自動連番GenerateIdentityidentity({0}, 1)generated by default as identity (start with {0} increment by 1)空文字(主キー制約追加後に別途 auto_increment を設定)
現在日時CurrentDateTimegetdate()CURRENT_TIMESTAMPCURRENT_TIMESTAMP(3)
NULL 置換IsNullisnullcoalesceifnull
LIKELikelikeilike(大文字小文字を区別しない)like
真 / 偽TrueString / FalseString1 / 0true / false1 / 0

1.5.8.1 のソースで確認した値です(SqlServerSqls.cs#L9-L42、PostgreSqlSqls.cs#L10-L42、MySqlSqls.cs#L11-L43)。IDENTITY の開始値は定義の Seed で、0 のときは 1 になります(Columns.cs#L156-L159)。

設定値(ISqlDefinitionSetting)SQL ServerPostgreSQLMySQL
IdentifierPostfixLength643232
NationalCharacterStoredSizeCoefficient244
ReducedVarcharLength00760
SchemaName空(dbo)上記のとおり決まる空

データ型の自動変換 ​

カラム定義の型名は SQL Server の型名で書かれており、PostgreSQL と MySQL では作成時に Convert() で置き換え、DB と比べるときは ConvertBack() で SQL Server の型名に戻します(PostgreSqlDataTypes.cs#L8-L45、MySqlDataTypes.cs#L7-L41)。

定義上の型PostgreSQLMySQL
ncharcharchar
nvarchar(max)textlongtext
nvarcharvarcharvarchar(1024 以上は下記)
bitbooleantinyint(1)
varbinarybyteablob
imagebytealongblob
datetimetimestamp(3)datetime(3)

MySQL では、MaxLength が 1024 以上の nvarchar を次のように作ります(MySqlColumns.cs#L9-L79)。

条件作る型理由
既定値(Default)があるvarchar(760)(ReducedVarcharLength)text 型には既定値を設定できない
インデックスの列になっているvarchar(760)text 型のインデックスにはサイズ制限がある
上記以外textより大きなデータを格納できる

パラメータの引き継ぎ(merge) ​

merge は、バージョンアップのときに旧環境の App_Data/Parameters を新しいバージョンの形に合わせて引き継ぐコマンドです。1.5.8.1 のソースでの動きは次のとおりです(Starter.cs#L240-L347、PatchParameters.cs#L15-L60)。

text
dotnet Implem.CodeDefiner.dll merge /b <旧環境のフォルダ> [/i <新環境のフォルダ>]

図を読み込み中…

  • /i を省略すると、新環境は Windows なら C:\web\pleasanter、それ以外は /web/pleasanter、パッチは そのフォルダの ParametersPatch.zip になります。/i を指定したときも、パッチは /i のフォルダの ParametersPatch.zip です(DefaultParameters.cs)。zip が無ければ FileNotFoundException です。
  • バージョンは各桁を 2 桁にそろえた文字列(1.5.8.1 → 01.05.08.01)にして、zip 内の ParametersPatch/{バージョン} フォルダ名と突き合わせます。フォルダは名前の昇順に並べ、旧バージョンのフォルダの次から新バージョンのフォルダまでを順に適用します。ダウングレードはできず、zip に旧バージョンのフォルダが無いバージョン(開発版など)からは引き継げません。
  • パッチは JSON の差分で、JsonDiffPatch.Net(2.5.0)の差分形式です(RFC 6902 の JSON Patch ではありません)。Parameters 配下(サブフォルダを含む)の各ファイルに、パッチフォルダ内のファイル名が同じファイルを当てます。フォルダの位置は見ないので、サブフォルダに同じ名前のファイルがあればそれにも当たります。
  • 適用後のファイルは 4 スペースのインデントで書き直されます。すべて終わると Patch application was successful. と新バージョンが表示されます。

途中で失敗すると中途半端な状態で残る

JToken.Parse() とパッチの適用に例外処理が無く、ロールバックもありません。Parameters の JSON にコメントや末尾のカンマなど JToken.Parse() が受け付けない書き方があると、そのファイルで例外になり、それまでに書き込んだファイルだけがパッチ済みの状態で残ります。配列の要素を追加・削除して使っている項目では、差分のインデックスがずれて意図しない結果になることがあります。merge の前に新環境の Parameters を残しておき、終わったら差分を確認してください。

引き継ぎのあとで起動するとき、必須のパラメータ(required: true で読むもの)が JSON として読めなければ、起動時に ParametersIllegalSyntaxException になります。一方、Quartz・BackgroundJobs など required: false で読むパラメータは、読めなくても例外にならず null(一部は既定のインスタンス)で動きます(Initializer.cs#L339-L354、Jsons.cs#L22-L32)。

バージョンごとのパッチに頼らない引き継ぎ方の設計は パラメータを差分の JSON だけで持つ を参照してください。

実践的なポイント ​

  • 新しいカラムを追加する: Definition_Column/ に JSON ファイルを追加し、CodeDefiner の def コマンドを実行します。
  • 手動修正を保護する: メソッドの直前に /// Fixed: コメントを付けます。マージ対象から完全に外すなら /// NotMerge: です。
  • 特定テーブルだけにコードを生成する: 定義 JSON に Include / Exclude を指定します。
  • 生成をデバッグする: /t オプションで特定の定義 ID だけを再生成して確認します。
  • テンプレートで新しいカラム属性を使う: カラム定義のプロパティ名がそのまま動的プレースホルダー(#プロパティ名#)として使えます。
  • 生成物の差分を確認する: 変更のないファイルは書き込まれず、コンソールに - と表示されます。
  • バージョンアップ前に作り直されるテーブルを見る: _rds /c で、DB を変えずに作成・移行の対象を確認できます。
  • 移行後の旧テーブルに注意する: 移行で退避された _Migrated_ で始まるテーブルは自動では削除されず、DB に残り続けます。

関連ページ ​

変更履歴

第4版CodeDefiner のデータベース作成・更新とパラメータの引き継ぎ、画面でのパラメータ管理、MCP エンドポイントのブラウザアクセスの解説と、関連する改修・設計メモを追加
第3版「内部実装を読む」を 1.5.8.1 のソースで検証して修正
第2版記事のファイル名に並び順の番号を付け、元記事リンクを frontmatter の sources に移行
第1版「内部実装を読む」に CodeDefiner・多言語対応・ライセンス判定・拡張計算式を追加