【Apex】recalculateFormulasメソッドまとめ

# はじめに
Formulaクラス・SObjectクラスで使用できるrecalculateFormulasメソッドについての解説記事です。
数式項目を再計算できるこのメソッドですが、曖昧なまま使っていると以外と嵌ります。
そこで各クラスで使用できるメソッドの相違点を含め、色々なパターンを検証してみました。

[・Apex 開発者ガイド | Formula クラス](https://developer.salesforce.com/docs/atlas.ja-jp.apexcode.meta/apexcode/apex_class_System_Formula.htm)
[・Apex 開発者ガイド | SObject クラス](https://developer.salesforce.com/docs/atlas.ja-jp.apexcode.meta/apexcode/apex_methods_system_sobject.htm#apex_System_SObject_putSObject)

# 基本
その時点でレコードが保有する項目に応じて数式項目を再計算できるメソッドです。

```apex
// 単一のSObject
SObject.recalculateFormulas();
// SObjectのList
Formula.recalculateFormulas(SObjectList);
// FormulaRecalcResultクラスとして返り値を処理する場合
List<FormulaRecalcResult> results = Formula.recalculateFormulas(SObjectList);
```

以下の例では、`UnitPrice__c(単価) * Quantity__c(数量)`を表す数式項目`TotalAmount__c(合計金額)`を更新し、前後の値を比較しています。

```apex
Child__c c = [SELECT Id, UnitPrice__c, Quantity__c, TotalAmount__c FROM Child__c WHERE Name = 'child1' LIMIT 1];
System.debug('UnitPrice__c：' + c.UnitPrice__c);
System.debug('Quantity__c：' + c.Quantity__c);
System.debug('変更前 TotalAmount__c：' + c.TotalAmount__c);
c.Quantity__c = 5;
update c;
System.debug('update後 TotalAmount__c：' + c.TotalAmount__c);
c.recalculateFormulas();
System.debug('再計算後 TotalAmount__c：' + c.TotalAmount__c);
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/573865/731f7088-f58b-de49-5ceb-496d5bea5001.png)

update直後の7行目は合計金額が100のままでしたが、`recalculateFormulas()`を実行した9行目では値が再計算され500に更新されています。
`recalculateFormulas()`を使う代わりにSOQLでレコードを再取得すれば同様の結果が得られますが、数式項目を更新する目的のみなのであれば、意図を明確にするためにもこのメソッドを使いましょう。

### ※補足
- `recalculateFormulas()`を呼び出すと、SOQLクエリの呼び出しとしてカウントされるためガバナ制限に注意
- Apexトリガ内のTrigger.new(Map)では再計算された後の値を参照できるため使用する必要無し

# 検証
親レコードの項目を参照するクロスオブジェクト数式や積み上げ集計など、様々なパターンを検証してみます。
### 1. クロスオブジェクト数式項目
```apex
// Child__cのParentTotalAmountWithoutTax__cはParent__r.TotalAmountWithoutTax__cを参照する数式項目
// Parent__cのTotalAmountWithoutTax__cはUnitPrice__c * Quantity__cの数式項目
Child__c c = [SELECT Id, UnitPrice__c, Quantity__c, TotalAmount__c, ParentTotalAmountWithoutTax__c, Parent__c FROM Child__c WHERE Name = 'child1' LIMIT 1];
Parent__c p = [SELECT Id, UnitPrice__c, Quantity__c, TotalAmountWithoutTax__c FROM Parent__c WHERE Id = :c.Parent__c LIMIT 1];

System.debug('変更前 TotalAmountWithoutTax__c：' + p.TotalAmountWithoutTax__c);
p.Quantity__c = 5;
update p;
p.recalculateFormulas();
System.debug('変更後 TotalAmountWithoutTax__c：' + p.TotalAmountWithoutTax__c);

System.debug('再計算前 ParentTotalAmountWithoutTax__c：' + c.ParentTotalAmountWithoutTax__c);
c.recalculateFormulas();
System.debug('再計算後 ParentTotalAmountWithoutTax__c：' + c.ParentTotalAmountWithoutTax__c);
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/573865/8f329711-a859-8211-b1a0-eaf533996000.png)

親レコードを参照するクロスオブジェクト数式項目を子レコードから再計算してみました。
親レコードは数量の変更が反映されていますが、子レコードは再計算後も値が変わりません。
この挙動は開発者ガイドにも記載されている通りです。
> このメソッドは、クロスオブジェクト数式を再計算しません。クロスオブジェクト数式項目と非クロスオブジェクト数式項目の両方があるオブジェクトでこのメソッドをコールすると、非クロスオブジェクト数式項目のみが再計算されます。
[Apex 開発者ガイド | SObject クラス](https://developer.salesforce.com/docs/atlas.ja-jp.apexcode.meta/apexcode/apex_methods_system_sobject.htm#apex_System_SObject_recalculateFormulas)

しかし、Formulaクラス側には以下のように記載されています。
> 入力 sObject のすべての数式項目を更新 (再計算) します。
[Apex 開発者ガイド | Formula クラス](https://developer.salesforce.com/docs/atlas.ja-jp.apexcode.meta/apexcode/apex_class_System_Formula.htm)

明記していないだけでほぼ同じメソッドだろうなと思いつつも検証してみたところ、、、

```apex
Child__c c = [SELECT Id, UnitPrice__c, Quantity__c, TotalAmount__c, ParentTotalAmountWithoutTax__c, Parent__c FROM Child__c WHERE Name = 'child1' LIMIT 1];
Parent__c p = [SELECT Id, UnitPrice__c, Quantity__c, TotalAmountWithoutTax__c FROM Parent__c WHERE Id = :c.Parent__c LIMIT 1];

System.debug('変更前 TotalAmountWithoutTax__c：' + p.TotalAmountWithoutTax__c);
p.Quantity__c = 5;
update p;
p.recalculateFormulas();
System.debug('変更後 TotalAmountWithoutTax__c：' + p.TotalAmountWithoutTax__c);

System.debug('再計算前 ParentTotalAmountWithoutTax__c：' + c.ParentTotalAmountWithoutTax__c);
Formula.recalculateFormulas(new List<Child__c>{c}); //  ※変更箇所
System.debug('再計算後 ParentTotalAmountWithoutTax__c：' + c.ParentTotalAmountWithoutTax__c);
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/573865/a6667ded-7a5b-5bfc-e55d-6a451f3ba71c.png)

再計算されています！
ほぼ同じ挙動になると思っていたのでこの結果には驚きました。
クロスオブジェクト数式項目の再計算が必要な場合は、単一のSObjectであってもListへ格納してFormulaクラスのメソッドを使用しましょう。

### 2. 積み上げ集計項目


```apex
Child__c c = [SELECT Id, UnitPrice__c, Quantity__c, TotalAmount__c, Parent__c FROM Child__c WHERE Name = 'child1' LIMIT 1];
Parent__c p = [SELECT Id, UnitPrice__c, Quantity__c, StackedTotalAmount__c FROM Parent__c WHERE Id = :c.Parent__c LIMIT 1];

System.debug('変更前 StackedTotalAmount__c：' + p.StackedTotalAmount__c);
c.Quantity__c = 5;
update c;
System.debug('変更後 StackedTotalAmount__c：' + p.StackedTotalAmount__c);
p.recalculateFormulas();
System.debug('計算後その1 StackedTotalAmount__c：' + p.StackedTotalAmount__c);
Formula.recalculateFormulas(new List<Parent__c>{p});
System.debug('計算後その2 StackedTotalAmount__c：' + p.StackedTotalAmount__c);
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/573865/09326162-5530-2cdf-8414-ad2e42fe6e3e.png)

積み上げ集計と数式項目はやはり別物なようで、update直後に`c.recalculateFormulas()`を入れてみても無駄でした。

ならばと積み上げ集計を参照する数式項目を作成して再チャレンジしてみました。

```apex
Child__c c = [SELECT Id, UnitPrice__c, Quantity__c, TotalAmount__c, Parent__c FROM Child__c WHERE Name = 'child1' LIMIT 1];
Parent__c p = [SELECT Id, UnitPrice__c, Quantity__c, FormulaStackedTotalAmount__c FROM Parent__c WHERE Id = :c.Parent__c LIMIT 1];

System.debug('変更前 FormulaStackedTotalAmount__c：' + p.FormulaStackedTotalAmount__c);
c.Quantity__c = 5;
update c;
c.recalculateFormulas();
System.debug('変更後 FormulaStackedTotalAmount__c：' + p.FormulaStackedTotalAmount__c);
p.recalculateFormulas();
System.debug('計算後その1 FormulaStackedTotalAmount__c：' + p.FormulaStackedTotalAmount__c);
Formula.recalculateFormulas(new List<Parent__c>{p});
System.debug('計算後その2 FormulaStackedTotalAmount__c：' + p.FormulaStackedTotalAmount__c);
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/573865/e44aa774-7526-1d34-db1d-d1ebda65992b.png)

どうやら数式項目であれば積み上げ集計も再計算できるようです。
このようなユースケースの場合は、少し面倒ですが積み上げ集計を参照する数式項目を作成して対応しましょう。