Lookup の転記と上書き制御
Lookup(ルックアップ)は、リンク項目で選んだレコードから、現在のレコードの別項目へ値を転記する機能です。候補を絞る項目連携や ColumnFilterExpressions とは処理の目的が違います。
転記の可否は、保存済みの値だけでなく、送信したフォームに転記先の値が含まれるか、どの項目が通信を起こしたかによっても変わります。 Overwrite と OverwriteForm は、別の判定に使われます。
公式マニュアルとの関係
設定方法・対応する項目・オプションの基本は、公式のルックアップを参照してください。このページでは、記録テーブルの SetByLookups と Lookups.LookupData を中心に、転記判定と画面反映を説明します。確認は本体ソースによるもので、実機の画面操作は検証していません。
商品の単価を転記する構成例
商品マスタをサイト ID 200 とし、編集中のテーブルでは ClassB に商品、NumA に単価を設定する例です。サイト ID はダミーです。商品マスタ側にも単価の NumA があるものとします。
ClassB の選択肢一覧に、次の構成を設定します。
[
{
"SiteId": 200,
"Lookups": [
{
"From": "NumA",
"To": "NumA",
"Type": 0,
"OverwriteForm": true
}
]
}
]From は参照先の列、To は現在のテーブルの列です。Type は 0 が値、1 が表示名です(Lookup の設定項目)。
編集直後に単価を画面へ反映する構成では、商品を選ぶ ClassB の自動ポストバックを有効にします。返却する項目を限定している場合は、転記先の NumA も含めます。Lookup の設定自体は、変更イベントを登録する処理ではありません(自動ポストバックの送信、返却対象)。
転記はサーバーのモデルで行う
フォームを使う記録テーブルのモデル生成では、フォーム値の反映後に SetByLookups を呼ぶ経路があります。SetByLookups は、Lookup 設定があり、リンク項目が更新対象になっているか、要求のフォームにリンク項目のキーがあるものを対象にします(モデル生成、SetByLookups)。
Lookup の対象は、設定のあるすべてのリンクではありません。リンク項目が更新対象か、フォームにそのキーがあるかも確認します。
.Where(link => link.Lookups?.Any() == true)
.Where(link => PropertyUpdated(
context: context,
name: link.ColumnName)
|| context.Forms.ContainsKey($"{ss.ReferenceType}_{link.ColumnName}"))PropertyUpdated と ContainsKey は OR 条件です。保存済みのリンク先だけを変わらず参照していても、フォームの要求にリンク項目が含まれれば対象になります。逆に Lookup 設定があるという理由だけで、すべての要求で全リンクの転記が行われるわけではありません。
処理は、リンク項目の値を参照先の ID として読み、LookupData で転記する値の辞書を作り、その辞書を SetByForm で現在のモデルに反映します。_dispatch.js が参照先の単価を取得しているわけではありません。
図を読み込み中…
Lookup はサーバーのモデル処理であり、自動ポストバック専用のブラウザ機能ではありません。画面へ即時反映する通信と、値を決める処理を分けて考えます。
Overwrite と OverwriteForm の判定
LookupData は、転記先ごとに次の二つの条件を両方満たすかを判定します(転記対象の絞り込み)。
二つの上書き条件は、一つの OR 式ではなく、連続する Where です。最初の条件を通った転記先だけが、次の条件へ進みます。
.Where(lookup => lookup.Overwrite != false
|| blankColumns.Contains(lookup.To)
|| formData.Get($"{ss.ReferenceType}_{lookup.To}") == string.Empty)
.Where(lookup => (lookup.OverwriteForm == true
&& formData.Get("ControlId") == $"{ss.ReferenceType}_{link.ColumnName}")
|| formData?.ContainsKey($"{ss.ReferenceType}_{lookup.To}") != true
|| (isNewRecord && IsDefaultValue(
context: context,
ss: ss,
formData: formData,
lookup: lookup)))
.ToList();OverwriteForm=true は、発火元がこのリンク項目であることと組で判定されます。また、Overwrite=false の条件を迂回する設定ではありません。例えば保存済みの NumA が空ではなく、要求の Results_NumA も空文字でない場合、最初の Where で除外されるので、二つ目を満たしても転記されません。一方、転記先がフォームにない場合は二つ目の条件を通れますが、最初の上書き可否の条件は引き続き必要です。
| 判定 | 転記を許可する条件 |
|---|---|
Overwrite 側 | Overwrite が false ではない、または転記先が空値判定のリストに含まれる、または送信された転記先が空文字 |
OverwriteForm 側 | OverwriteForm=true で発火元の ControlId がそのリンク項目、またはフォームに転記先のキーがない、または新規レコードで転記先の送信値が既定値と判定される |
Overwrite は未指定なら false として扱われません。OverwriteForm は未指定でも、転記先がフォームに含まれない場合などには転記を許可します。「OverwriteForm がないと Lookup が一切動かない」という判定ではありません。
手入力した転記先との関係
例えば、単価 NumA を手入力して送信バッファに格納したあと、商品 ClassB を変更した場合です。
| 設定と条件 | 判定 |
|---|---|
Overwrite 未指定、OverwriteForm 未指定、フォームに非既定値の NumA がある | フォーム値の判定で転記対象から外れる |
Overwrite 未指定、OverwriteForm=true、発火元が ClassB | 商品からの転記がフォーム値より優先される |
Overwrite=false、OverwriteForm=true、保存済み NumA は非空で、送信値も非空 | Overwrite 側の条件で転記対象から外れる |
OverwriteForm=true にしても、Overwrite=false の判定を無条件に無視するわけではありません。また、発火元が別の項目なら OverwriteForm=true の発火元条件を満たしません。
空値と既定値は別の判定
保存済みレコードでは、SetByLookups が保存済みの転記先の値を Column.BlankValue で判定します。未保存のモデルでは現在の値を使います。空値の扱いは型によって違い、例えば空を許可しない数値では 0、チェックでは false も空値側になります(空値リストの生成、BlankValue)。
新規レコードのフォーム値については、別に IsDefaultValue が判定します。設定された既定値との一致、既定値がない場合の空文字、空を許可しない数値の 0、チェックの false などを扱います(IsDefaultValue)。
画面へ返すときにバッファも処理する
自動ポストバックの FieldResponse は、LookupClearFormData を呼びます。この処理は、発火元のリンクのうち OverwriteForm=true の転記先へ ClearFormData を返します。その後、返却対象の転記先を値や HTML として返します(FieldResponse の先頭、LookupClearFormData)。
画面への応答では、発火元のリンクと OverwriteForm の設定から、バッファを削除する命令を作ります。
ss.Links
?.Where(link => $"{ss.ReferenceType}_{link.ColumnName}" == context.Forms.ControlId())
.Where(link => link.Lookups != null)
.SelectMany(link => link.Lookups)
.Where(lookup => lookup.OverwriteForm == true)
.ForEach(lookup => res.ClearFormData($"{ss.ReferenceType}_{lookup.To}"));ここには「実際に Lookup の転記が成功したか」の結果を読む条件がありません。対象のキーが削除されたことだけでは、転記成功の証拠にはなりません。応答の ClearFormData と、値や HTML を返す命令を別々に確認します。
| 応答 | 役割 |
|---|---|
ClearFormData | 送信バッファにある転記先の値を削除 |
ReplaceAll | Lookup の転記先など、差し替え対象のフィールドを再描画 |
SetValue | フィールドを差し替えない場合に表示値を設定 |
Invoke("initRelatingColumnEditorNoSend") | 項目連携の属性を送信なしで再設定 |
発火元のリンクの Lookups の転記先は、ReplaceFieldColumns に加わります。ただし、返却対象を絞る設定があれば、その対象に含まれる項目だけが反映されます(差し替え対象)。
ClearFormData は画面の入力値を空にする命令ではありません。_dispatch.js が $p.clearData を呼び、送信データのキーを削除します。また、LookupClearFormData は転記が実際に行われたかを再判定せず、発火元と OverwriteForm の設定で命令を生成します(命令の実行、バッファの削除)。
転記されない・空になるときの確認点
- リンク項目が対象か —
SetByLookupsの対象になる更新状態、フォーム内のリンク項目のキーを確認します。 - 転記先を手入力していないか — フォームに転記先のキーがある場合は、
OverwriteFormと発火元を確認します。 - 上書きを禁止していないか —
Overwrite=falseと空値判定を確認します。 - 参照先を読めるか — 通常のテーブルではレコードの参照権限を確認し、転記元の列についても読取権限を判定します(
レコードの判定、転記元の列の判定)。 - 転記先が返却対象か — 自動ポストバック時の返却対象と、応答の
ReplaceAll・SetValueを確認します。
転記対象となった列の辞書は、まず空文字で作られます。参照 ID が正数で、参照先の設定があり、読み取りが許可される場合に取得値で置き換えます。リンクの解除などで取得条件を満たさない場合、転記対象の値は空文字のまま返されます。「取得できなければ必ず元の値が残る」という動作ではありません(LookupData)。
Lookup は先に転記対象の辞書を空文字で作り、参照先を読める場合に取得値へ置き換えます。
var changedFormData = lookups.ToDictionary(
lookup => $"{ss.ReferenceType}_{lookup.To}",
lookup => string.Empty);
if (id > 0
&& currentSs != null
&& canRead)リンクを解除して ID が正数でなくなる場合も、上書き条件を通った転記先のキーは辞書にあります。その値は空文字のままです。「参照できないので何もしない」と「空文字を転記する」は同じではありません。値が消えたときは参照 ID と上書き条件を両方調べます。
複数のリンクから同じ転記先へ値を返す場合は、SetByLookups がリンクを ControlledOrder に従って並べ、同じ転記先のキーでは先頭の結果を採用します。設定の競合も確認してください(転記結果の集約)。