Recalculating Salesforce Formula Fields in Apex

Katie - Sep 10 '19 - - Dev Community

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:

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .