こんにちは、Nambuです。 今回はApexトリガを作成する際に気を付けたい点について説明したいと思います。もしこのことを知らず何気なく使ってしまうと思った通りの結果が得られない可能性があるので注意が必要です。
問題の紹介
まず、前提として取引先と取引先責任者にはそれぞれ「はてなブログ取引先」と「はてなブログ取引先責任者」のレコードしかないとします。
さて皆さんがApexでプログラムを書いている場合、レコードの項目の値と親オブジェクトの項目の値は以下のように取得してから使用すると思います。
public class cntl_hatenaImportantPoint { public void test(){ List <Contact> conList = [SELECT LastName, FirstName, Account.Name From Contact]; System.debug('Contact Name : '+ conList.get(0).LastName + conList.get(0).FirstName); System.debug('Account Name : '+ conList.get(0).Account.Name); } }
実際に上記のメソッドをアノニマスウィンドウにて実行してみるとデバッグログにはそれぞれ「はてなブログ取引先責任者」と「はてなブログ取引先」が出力されます。
では次に皆さんがApexトリガを書いているとします。今回例に出すのは取引先責任者を対象としたトリガです。トリガではTrigger.newやTrigger.oldを用いることで変更前後のデータを扱うことができるので、これらを使ってApexで書いたように項目の値と親オブジェクトの項目の値を使用しようとします。
trigger trg_hatenaImportantPoint on Contact (after update) { if(Trigger.isUpdate){ for(Contact con : trigger.old){ System.debug('Trigger old'); System.debug('Contact Name : '+ con.LastName + con.FirstName); System.debug('Account Name : '+ con.Account.Name); } for(Contact con : trigger.new){ System.debug('Trigger new'); System.debug('Contact Name : '+ con.LastName + con.FirstName); System.debug('Account Name : '+ con.Account.Name); } } }
トリガを実行するために「はてなブログ取引先責任者」レコードを空更新してみます。
すると、取引先責任者のLastNameとFirstNameは問題なく出力されていますが、取引先名の所はnullとなっています。
今回の原因と解決策
今回取引先名の部分がnullとなってしまった原因はTrigger.newやTrigger.oldでは参照先を辿って値を取得できないという点にあります。トリガを書く機会はそうないと思うので意外と知らなかったのではないでしょうか?
では、どうすれば親オブジェクトの値、今回で言えば取引先名を使用できるのでしょうか。答えは単純でSOQLを使ってレコードを取得するです。
trigger trg_hatenaImportantPoint on Contact (after update) { if(Trigger.isUpdate){ Set <Id> oldAccId = new Set <Id>(); for (Contact con : trigger.old){ oldAccId.add(con.AccountId); } List <Account> oldAccRecords = [SELECT Id, Name FROM Account WHERE Id IN :oldAccId]; Map<Id, Account> oldAccRecordMap = new Map<Id, Account>(oldAccRecords); for(Contact con : trigger.old){ System.debug('Trigger old'); System.debug('Contact Name : '+ con.LastName + con.FirstName); System.debug('Account Name : '+ oldAccRecordMap.get(con.AccountId).Name); } Set <Id> newAccId = new Set <Id>(); for (Contact con : trigger.new){ newAccId.add(con.AccountId); } List <Account> newAccRecords = [SELECT Id, Name FROM Account WHERE Id IN :newAccId]; Map<Id, Account> newAccRecordMap = new Map<Id, Account>(newAccRecords); for(Contact con : trigger.new){ System.debug('Trigger new'); System.debug('Contact Name : '+ con.LastName + con.FirstName); System.debug('Account Name : '+ newAccRecordMap.get(con.AccountId).Name); } } }
SOQLで取得するよう変更したコードで再びレコードを空更新したときのデバッグログを見てみると、今回は取引先名がnullになることなく「はてなブログ取引先」と出力されています。
おまけ
取引先責任者において名前は「姓」「名」に分かれているので作成や更新に使用する場合はLastName、FirstNameを使うことが多いと思います。しかし、SOQLでNameと指定するとLastNameとFirstNameが連結された文字列が取得できます。
public class cntl_hatenaImportantPoint { public void test(){ List <Contact> conList = [SELECT Name, Account.Name From Contact]; System.debug('Contact Name : '+ conList.get(0).Name); System.debug('Account Name : '+ conList.get(0).Account.Name); } }
実際にアノニマスウィンドウから実行した結果のデバッグログでも「はてなブログ取引先責任者」となっています。
では、Apexトリガのコンテキスト変数でも同じようにNameを使用してみます。
trigger trg_hatenaImportantPoint on Contact (after update) { if(Trigger.isUpdate){ for(Contact con : trigger.old){ System.debug('Trigger old'); System.debug('Contact Name : '+ con.Name); System.debug('Account Name : '+ con.Account.Name); } for(Contact con : trigger.new){ System.debug('Trigger new'); System.debug('Contact Name : '+ con.Name); System.debug('Account Name : '+ con.Account.Name); } } }
すると、デバッグログの結果ではnullと出力されました。ほとんど活きる機会はないと思いますがトリガのコンテキスト変数では取引先責任者のNameは使えないと覚えておいてください。
今回はこのくらいで、また会いましょう!