事の発端
sfdx force:data:tree:importのお話です。
普通は画面でデータ作ってdata:tree:exportしてそれをimportに渡したらいいんだけど、
データの関連が3階層まで及ぶと厄介である事に気づいてしまった・・・
スクラッチ組織の環境構築で必要なテストデータ作るときに、困ったこと。
なんというか、SOQLが3階層の連携だとエラーになるの。。。
エラー例)
SELECT Id
, mynamespace__AAA__c
, mynamespace__BBB__c
, mynamespace__CCC__c
,(
SELECT Id
, mynamespace__DDD__c
, mynamespace__EEE__c
, mynamespace__FFF__c
, (
SELECT Id
, mynamespace__GGG__c
FROM mynamespace__CObjects__r
)
FROM mynamespace__BObjects__r
)
FROM mynamespace__AObject__c
SOQL statements cannot query aggregate relationships more than 1 level away from the root entity object.
エラーメッセージ内容は書いている通り。
改めて、SOQLには「親-子リレーションの 1 つのレベルだけが 1 つのクエリ内で指定可能」という制約があった。
純粋にApexクラスの中で処理を実装するだけならば、2回に分けて実装すれば解決する。
しかし、
『 スクラッチ組織上でテストデータを作った!
そのテストデータ環境をメンバー間で使いまわしたい! 』
という場合は、ちょっと面倒であることに気づいた。
ちょっとSOQLを知らない人向けに補足
ちょっとSOQLを知らない人向けに補足(不要な人はスキップ)
SOQLは、Salesforce Object Query Language の略で、
Salesforce独特のデータ問い合わせコマンドです。
SQLに似ているけどSQLではないことがポイント
取得した結果は、ただの配列という形ではなく、JSON
形式のようにも取得できるし、Map<Id,Record>
形式のように取得することも、List
形式で取得する事のも可能。
なお、今回の事例は以下のように実装すると動きます。
SELECT Id
, mynamespace__AAA__c
, mynamespace__BBB__c
, mynamespace__CCC__c
,(
SELECT Id
, mynamespace__DDD__c
, mynamespace__EEE__c
, mynamespace__FFF__c
FROM mynamespace__BObjects__r
)
FROM mynamespace__AObject__c
上記のSOQLを実行した結果は、以下のようになります。
mynamespace__AObject__c: [
{ Id: 'xxx'
, mynamespace__AAA__c: 'xxx'
, mynamespace__BBB__c: 'xxx'
, mynamespace__CCC__c: 'xxx'
, mynamespace__BObjects__r: [
{ Id: 'bbb'
, mynamespace__DDD__c: 'bbb'
, mynamespace__EEE__c: 'bbb'
, mynamespace__FFF__c: 'bbb'
},
{...略},
{...略},
{...略} ]
},
{...略},
{...略},
{...略} ]
SOQLは便利な反面、厳密なクエリ制限があるのです。
簡単な補足終了!
では、3階層のデータを入れる為には何をするのか。
ヒントは以下の公式サイトの一番したに記載があります。
https://developer.salesforce.com/docs/atlas.ja-jp.sfdx_dev.meta/sfdx_dev/sfdx_dev_test_data_example.htm
もっと複雑な例をお探しですか? easy-spaces-lwc サンプルアプリケーションには、アカウント、関連取引先責任者、および 3 レベルの深度のカスタムオブジェクトチェーンをインポートする方法を示すデータプランがあります。
そう!
これだけしか記載がないのである!!!
なんて、クレイジー!
じゃあ easy-spaces-lwc で分かること
私の求めている情報が正に、Reservation__c
の存在だ。
さて、この指定されたリンク先の Plan2.jsonというファイルは、プラン実行ファイルである。
[
{
"sobject": "Account",
"saveRefs": true,
"resolveRefs": false,
"files": ["Accounts.json"]
},
{
"sobject": "Contact",
"saveRefs": true,
"resolveRefs": true,
"files": ["Contacts.json"]
},
{
"sobject": "Market__c",
"saveRefs": true,
"resolveRefs": false,
"files": ["Market__cs.json"]
},
{
"sobject": "Space__c",
"saveRefs": false,
"resolveRefs": true,
"files": ["Space__cs.json"]
},
{
"sobject": "Reservation__c",
"saveRefs": false,
"resolveRefs": true,
"files": ["Reservation__cs.json"]
}
]
中身を読み解くと、以下の属性を持っている。
sobject
saveRefs
resolveRefs
files
sobject
は、Salesforce上のオブジェクトマネージャーにも表示されたオブジェクトを表すキーワードであることがわかる。
files
は、実際のテストデータを記載したファイルが含まれている。
{
"attributes": {
"type": "Account",
"referenceId": "AccountRef1"
},
"Name": "Thompson, Skidmore and Smith"
},
{
"attributes": {
"type": "Contact",
"referenceId": "ContactRef1"
},
"Email": "phaidra.rayer@sw-global.com",
"FirstName": "Phaidra",
"LastName": "Rayer",
"MailingCity": "San Bruno",
"MailingCountry": "US",
"MailingPostalCode": "94066",
"MailingState": "CA",
"MailingStreet": "87 Northland Place",
"MobilePhone": "+1 (408) 505-1578",
"AccountId": "@AccountRef1",
"Reservation_Status__c": "Draft"
},
{
"attributes": {
"type": "Reservation__c",
"referenceId": "Reservation__cRef1"
},
"Contact__c": "@ContactRef1",
"Market__c": "@Market__cRef6",
"Start_Date__c": "2018-08-30",
"End_Date__c": "2018-09-30",
"Status__c": "Draft",
"Total_Number_of_Guests__c": 14
},
キーワードはreferenceId
である。そして、referenceId
で指定した値を@で始まる文字列
で指定しているところに注目
サンプルでは、
Account
オブジェクトの"referenceId": "AccountRef1"
を、
Contact
オブジェクトの"AccountId": "@AccountRef1"
という形式で参照している。
また、Contact
オブジェクトの"referenceId": "ContactRef1"
を、
Reservation__c
オブジェクトの"Contact__c": "@ContactRef1"
という形式で参照している。
改めて、3階層くらい関連したオブジェクトをスクラッチ組織にインポートするためには、このような関連情報を記載したファイルを作成すれば良いのである!!!
あとは、以下のようなSFDXコマンドの実行でOK!(以下は指定のサンプルに記載がありました)
7.Load sample data:
sfdx force:data:tree:import -p ./data/Plan2.json
でも、じゃあ saveRef と resolveRef って何?
上記のプラン実行ファイルを眺めていると、saveRef
とresolveRef
ってキーワードが出てくる。これ何?って思ったから調べたことを以下に整理しました。
なお整理に当たっては、以下やりとりを参考にしております。
https://salesforce.stackexchange.com/questions/212034/what-are-saverefs-and-resolverefs?noredirect=1&lq=1
saveRef
referenceId
を保存するかどうかのフラグです。
【具体例】
基本的に、sfdx force:data:tree:import -p ./data/Plan2.json
を実行したとき、
テストデータの値に沿ってスクラッチ組織にデータを登録する。
その際、登録することで正しいID(0015000000abcde
)が発行される。
※内部では"Id": "0015000000abcde"
が登録されている。
このときスクラッチ組織上にてreferenceId
の情報を残すかどうかである。
■"saveRefs": falseの場合
"referenceId": "AccountRef1"
を削除する。
※ただし、後続のファイルで、ルックアップフィールドが@で始まる値
(例@AccountRef1
)を使用しようとしてもエラーとなります。
■"saveRefs": trueの場合
"referenceId": "AccountRef1"
を削除しない。
"AccountRef1"=0015000000abcde
という情報が保存される。
resolveRef
後続のファイルで、ルックアップフィールドが@で始まる値(例@AccountRef1
)を使用したとき、以前に保存した参照を解決するかどうかのフラグです。
【具体例】
■"resolveRef": falseの場合
@で始まる値
を参照情報として利用しない。入力値として扱う。
"AccountId": "@AccountRef1"
という情報は、そのまま文字列として指定する。
※ただし、もしこれが参照制約のあるフィールドの場合、エラーになります。
■"resolveRef": trueの場合
@で始まる値
を参照情報として利用する。
"AccountId": "@AccountRef1"
という情報は
"AccountRef1"=0015000000abcde
という情報として取り扱う。
判断基準
まとめ
3階層くらいの関連したレコードをスクラッチ組織へインポートする方法は、
以下の3つを意識した テストデータJSONファイル と、 プラン実行ファイル をimport
すれば良い!
-
referenceId
- 重複しないユニークなID文字列を任意に定義してあげる。
- 別から参照するときは、@を接頭辞に付けてあげる。
-
saveRefs
- 自身オブジェクトのIDが別オブジェクトから参照されるときTrueと指定が必要
-
resolveRef
- @を使用して、別オブジェクトのIDを参照しているときTrueと指定が必要)
以上です。ではでは!