Salesforce Apex: How to Track the Number of Contacts per Account
Tutorial using Apex Triggers and Classes
The number of contacts per account is one of the first things anybody new to setting up Salesforce is interested in tracking. Unfortunately, a simple roll-up summary is not possible because the Contact sObject is a standard object.
Being unable to solve this requirement with a simple field, we need to use some of the Developer tools in Salesforce to auto-magically maintain this relationship between Account and Contacts.
In this tutorial we’ll use an Apex Trigger as the primary mechanism for tracking the number of contacts. Adjacent to the trigger, we’ll write the majority of our code in an Apex Class, setting us up for future updates and expansion.
Project Plan and Setup
This project will have three primary steps:
- Add a field to the Account sObject
- Write the Apex Class to be called in the Trigger
- Create the Apex Trigger
We’ll go through one step at a time, but if you’re the tl;dr type then there are links to the Apex Trigger and Apex Class along with an important warning at the bottom article.
Number of Contacts Field
The first step is to create the field that will hold our contact count. Under the Object Manager in Setup, open the Account object and add a new Number field. To ensure the article’s code works seamlessly, name the field Number_of_Contacts and verify the API name is Number_of_Contacts__c.
Apex Handler Class
This is a bit of cheating because I began with the Apex Trigger when I first wrote this workflow, but for brevity let’s jump right into the Apex Class.
In case you’re brand new to all of this, we’ll create the Apex Class in the Developer Console and to keep with standard convention we should use the name ContactTriggerHandler.
Essentially, we want to update Number_of_Contacts whenever a contact is inserted, deleted, or undeleted using the list of contacts in the trigger.
The basic steps of the class are as follows:
- Determine the accounts that are affected based on the list of contacts
- Get the current contact count for each account
- Update the count for each contact based on the trigger operation
Let’s start with the entire skeleton of the Apex Class and flesh out the methods afterwards.public without sharing class ContactTriggerHandler {
private Set<Id> getAccountIds(List<Contact> contacts) {}
private Map<Id, Account> getAccountMap(Set<Id> accountIds) {}public void process(List<Contact> contacts, System.TriggerOperation operation) {}
}
getAccountIds()
This method is responsible for taking the list of contacts and determining which accounts were affected.private Set<Id> getAccountIds(List<Contact> contacts) {
Set<Id> accountIds = new Set<Id>();
for(Contact c : contacts) {
if(c.AccountId != null) {
accountIds.add(c.AccountId);
}
}
return accountIds;
}
We use a Set to avoid duplicates, keeping the SOQL query in the next step as light as possible. Additionally we filter out any contacts who are not related to any account.
getAccountMap()
This method is responsible for querying accounts whose Ids exist in our aforementioned Set and retrieving the number of contacts.private Map<Id, Account> getAccountMap(Set<Id> accountIds) {
return new Map<Id, Account>(
[
SELECT
Id,
Number_of_Contacts__c
FROM Account
WHERE Id in :accountIds
]
);
}
Notice we’ll be using the accountIds from the previous step to filter the SOQL results. The resulting data structure is a Map where the keys are the account Ids and the values are the corresponding Account objects.
process()
This method is responsible for the main flow of the handler and will be called in the Apex Trigger.public void process(List<Contact> contacts, System.TriggerOperation operation) {
Set<Id> accountIds = getAccountIds(contacts);
if(accountIds.size() > 0) {
Map<Id, Account> accountMap = getAccountMap(accountIds);
for(Contact c : contacts) {
if(accountMap.containsKey(c.AccountId)) {
Id id = c.AccountId;
switch on operation{
when AFTER_INSERT {
accountMap.get(id).Number_of_Contacts__c += 1;
}
when AFTER_DELETE {
accountMap.get(id).Number_of_Contacts__c -= 1;
}
when AFTER_UNDELETE {
accountMap.get(id).Number_of_Contacts__c += 1;
}
}
}
}
update accountMap.values();
}
}
The first couple lines utilize the getAccountIds() and getAccountMap() methods. Afterwards, we loop through our contacts and increment/decrement accordingly.
It isn’t until the very end that we update our entire accountMap. This process is called bulkifying and is a critical design pattern to work within Salesforce’s governor limits.
Apex Trigger
Final step, the Apex Trigger. Similar to the Apex Class, this is created in the Developer Console. Name it ContactTrigger and choose the Contact sObject.
The structure of an Apex Trigger is to list the different operation types which will invoke the trigger. As mentioned earlier, we want to update the count whenever a contact is inserted, deleted, or undeleted.trigger ContactTrigger on Contact (after insert, after delete, after undelete) {}
Now for the body of the trigger. We’ll begin by creating a new instance of the Apex Class we previously wrote. Then, based on the operation type we’ll call process() with the appropriate list of contacts. We use Trigger.old during delete because the contacts do not exist anymore once deleted.trigger ContactTrigger on Contact (after insert, after delete, after undelete) {
ContactTriggerHandler handler = new ContactTriggerHandler();
switch on Trigger.operationType {
when AFTER_INSERT {
handler.process(Trigger.new, Trigger.operationType);
}
when AFTER_DELETE {
handler.process(Trigger.old, Trigger.operationType);
}
when AFTER_UNDELETE {
handler.process(Trigger.new, Trigger.operationType);
}
}
}
Important Final Word
We’re now ready to test out our trigger. Well, almost. Assuming your environment is not completely barren, you have accounts with existing contacts and a NULL value for Number_of_Contacts.
The field needs to be back-filled and because it’s a one-time operation we’re going to use the execute the following code in an Anonymous Window within the Developer Console under Debug.Map<Id, Account> accounts = new Map<Id,Account>([
SELECT
Id,
Number_of_Contacts__c,
(
SELECT Id FROM Contacts
)
FROM Account
]);for(Id key : accounts.keySet()) {
Integer count = accounts.get(key).Contacts.size();
accounts.get(key).Number_of_Contacts__c = count;
}update accounts.values();
At this point, I would recommend to also set a default value of 0 for the Number_of_Contacts field.
You’re ready to rock and roll 🤘 Try out the trigger by adding a contact, deleting them, then undeleting, making sure to verify the count after each action.
As promised, here are links to the full code if you’d like to just copy/paste and move along.
- Apex Class: ContactTriggerHandler
- Apex Trigger: ContactTrigger
Please share your experiences, questions, and feedback below. Follow Code 85 for more plain language programming guides. Thanks for reading!