Salesforce Winter ‘20 has a new Formula
class in Apex – check out what it can do.
James Hou pointed out, quoted in Windyo’s excellent abridged Winter ‘20 release notes on Reddit :
Formulas recalculation in APEX: That is HUGE.
You can calculate formulas without a DML, in batch.
Let that sink in…. you can basically have formulas lift heavy calculations and do it in an Apex class _ without saving _.
And it’s true!
Here’s an example in the form of a unit test:
@isTest
public class ATest {
static testMethod void runTest() {
ParentObject __c parent = new ParentObject__ c(
Name = 'Parent',
Code__c = 'ABAB'
);
INSERT parent;
ChildObject __c child = new ChildObject__ c(
Name = 'Child',
Code__c = 'XYXY',
Parent__c = parent.Id
);
System.assertEquals(NULL, child.Concatenated_Formula_Field__c);
Test.startTest();
Formula.recalculateFormulas(new List<ChildObject__c>{child});
Test.stopTest();
System.assertEquals('ABAB|XYXY', child.Concatenated_Formula_Field__c);
}
}
Note that the object named child
does not exist yet in the database – it’s an in-memory SObject
only.
I have not yet saved it to the database with an INSERT
command.
And yet, after running Formula.recalculateFormulas()
, the value of the field Concatenated_Formula_Field__c
is ABAB|XYXY
.
Note: In this case, the formula itself is:
Parent__r.Code__c & "|" & Code__c
Error Message Workaround
FYI, if you try to use Formula.recalculateFormulas()
in a BEFORE
-context trigger to ensure that you condition logic upon the values of formula fields, you might get a System.DmlException
that says, UNKNOWN_EXCEPTION
and Cannot set the value of a calculated field
.
So far, the workaround I've come up with is to avoid actually running Formula.recalculateFormulas()
against the SObject
whose field values you actually intend to set with the trigger code.
If you need to read Contact.Formula_1__c
so as to decide what to put into Contact.Custom_Text_Field__c
, do the following:
private void setCustomField(List<Contact> contacts) {
Map<Contact, Contact> contactsAndTheirClones = new Map<Contact, Contact>();
for ( Contact c : contacts ) { contactsAndTheirClones.put(c, c.clone()); }
Formula.recalculateFormulas(contactsAndTheirClones.values());
for ( Contact c : contacts ) {
Contact cClone = contactsAndTheirClones.get(c);
if ( cClone.Formula_1__c == 'Hello' ) {
c.Custom_Text_Field__c = 'World';
}
}
}
Got a better idea? Have thoughts on whether this is better or worse than a small SOQL query in a before-trigger? Please comment; I'd love to hear it!
Further Reading:
- Recalculate formulas on the fly with the new Formula class – Salesforce Winter ’20 by Sebastián Kessel, who dives deeper into code examples