Français
Presentations About Resources

Salesforce, Python, SQL, & other ways to put your data where you need it -- a bilingual blog in English & French

Need event music? 🎸

Live and recorded jazz, pop, and meditative music for your virtual conference / Zoom wedding / yoga class / private party with quality sound and a smooth technical experience

Recalculating Salesforce Formula Fields in Apex

10 Sep 2019 🔖 salesforce apex tips tutorials
💬 EN ( Lire cet article en français )

Salesforce Winter ‘20 has a new Formula class in Apex – check out what it can do.

New Feature

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.

Notez: 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:

--- ---