Convert Existing Triggers to Allow For Bulk Processing

One of the most difficult concepts for me to get my head around after the Winter '10 release was that of bulk triggers. In the release documentation I read a line stating "All triggers are bulk triggers by default, and can process multiple records at a time."

Up until this release I had always written my triggers to allow for bulk processing but wasn't sure if this latest release meant that there was a need for me to update my existing Apex code to benefit from additional processing power. Of course, I was hoping there was something I could do to prevent hitting those annoying governor limits so I investigated further.

What I was able to determine through my deep dive was that I was not utilizing the Set and Map data structures in a manner that allowed for proper bulk processing. More specifically, most of my code was written so that it could not adequately process batches of several records at a time.

When looping through data sets I was typically iterating a record at a time. Although I was using Lists to keep my records organized and processing Data Manipulation Language (DML) operations outside of the loops I still frequently encountered governor limits.

So I became very aware of the need to make updates to my code in order to prevent iterating over records individually and allow for bulk processing of many records at a time. The best example I can think of to illustrate the required code changes was to take a trigger that I wrote to handle removal of credit card numbers on certain fields when a record is inserted or updated. Leaving the validation portion of the code out of this example I want to show you how the code worked initially.

/*
	Created by: Greg Hacic
	Last Update: 14 December 2009 by Greg Hacic
	Questions?: greg@interactiveties.com
	Copyright (c) 2009 Interactive Ties LLC
*/
trigger removeCCValue on Account (before insert, before update) {
	try {
		if (Trigger.isInsert) { //if fired from insert
			for (Account a : Trigger.new) { //for all records
				if (a.Description != null && a.Description.trim() != null) { //if the Description contains a value
					a.Description = ccValidator.checkField(a.Description); //remove credit card entries prior to committing data to Salesforce
				}
			}
		} else { //otherwise fired from update
			for (Integer i = 0; i < Trigger.new.size(); i++)  { //for all records
				if (Trigger.new[i].Description != null && Trigger.new[i].Description.trim() != null && Trigger.old[i].Description != Trigger.old[i].Description) { //if the Description contains a value and the value was, in fact, updated
					Trigger.new[i].Description = ccValidator.checkField(Trigger.new[i].Description); //remove credit card entries prior to committing data to Salesforce
				}
			}
		}
	} catch(Exception e) {
//		System.Debug('removeCCValue Trigger failed: '+e.getMessage()); //write error to the debug log
	}
}

The major issue with this version of the code is the FOR loop that runs when the trigger is fired via an update request. Specifically, the line reading "for (Integer i = 0 ; i < Trigger.new.size(); i++) {" is the portion of the code that is written incorrectly. Now the reason I wrote it this way initially was due to the fact that I wanted to compare the old record field value to the new record field value in order to make sure that the validation only happened is the value indeed changed.

So what did I do to allow for bulk processing? Please see the code below:

/*
	Created by: Greg Hacic
	Last Update: 14 December 2009 by Greg Hacic
	Questions?: greg@interactiveties.com
	Copyright (c) 2009 Interactive Ties LLC
*/
trigger removeCCValue on Account (before insert, before update) {
	try {
		for (Account a : Trigger.new) { //for all records
			if (a.Description != null && a.Description.trim() != null) { //if the Description contains a value
				if (Trigger.isUpdate) { //if fired from update
					if (a.Description != Trigger.oldMap.get(a.Id).Description) { //if the Description value was, in fact, updated
						a.Description = ccValidator.checkField(a.Description); //remove credit card entries prior to committing data to Salesforce
					}
				} else {
					a.Description = ccValidator.checkField(a.Description); //remove credit card entries prior to committing data to Salesforce
				}
			}
		}
	} catch(Exception e) { //if error is encountered
//		System.Debug('removeCCValue Trigger failed: '+e.getMessage()); //write error to the debug log
	}
}

The major change is utilizing the oldMap variable which allows me to loop through the sObjects in bulk and compare the old field value to the new value but not be explicit in the request as the code was earlier.

This subtle change in code has significantly reduced the likelihood of encountering governor limits when many records are processed and is pretty easy to implement.

Automated Exchange Rates in Salesforce.com

Reduce Repetitive Tasks, Eliminate Errors & Free Up Your Administrators.

Birthday Reminders for Salesforce.com

It might lead to a sale. Or it might make you feel good.