Search icon CANCEL
Subscription
0
Cart icon
Your Cart (0 item)
Close icon
You have no products in your basket yet
Arrow left icon
Explore Products
Best Sellers
New Releases
Books
Videos
Audiobooks
Learning Hub
Free Learning
Arrow right icon
Arrow up icon
GO TO TOP
Mastering Apex Programming

You're reading from   Mastering Apex Programming A developer's guide to learning advanced techniques and best practices for building robust Salesforce applications

Arrow left icon
Product type Paperback
Published in Nov 2020
Publisher Packt
ISBN-13 9781800200920
Length 368 pages
Edition 1st Edition
Languages
Arrow right icon
Author (1):
Arrow left icon
Paul Battisson Paul Battisson
Author Profile Icon Paul Battisson
Paul Battisson
Arrow right icon
View More author details
Toc

Table of Contents (21) Chapters Close

Preface 1. Section 1 – Triggers, Testing, and Security
2. Chapter 1: Common Apex Mistakes FREE CHAPTER 3. Chapter 2: Debugging Apex 4. Chapter 3: Triggers and Managing Trigger Execution 5. Chapter 4: Exceptions and Exception Handling 6. Chapter 5: Testing Apex Code 7. Chapter 6: Secure Apex Programming 8. Section 2 – Asynchronous Apex and Apex REST
9. Chapter 7: Utilizing Future Methods 10. Chapter 8: Working with Batch Apex 11. Chapter 9: Working with Queueable Apex 12. Chapter 10: Scheduling Apex Jobs 13. Chapter 11: Using Platform Events 14. Chapter 12: Apex REST and Custom Web Services 15. Section 3 – Apex Performance
16. Chapter 13: Performance and the Salesforce Governor Limits 17. Chapter 14: Performance Profiling 18. Chapter 15: Improving Apex Performance 19. Chapter 16: Performance and Application Architectures 20. Other Books You May Enjoy

Bulkification – querying within loops

Another common mistake seen in Apex is querying for data within loops. This is different than repeated querying for data, as discussed previously, but instead it focuses on performing a query with a (potentially) unique outcome for each iteration of a loop. This is particularly true within triggers.

When working with a trigger, you should always prepare your code to handle a batch of 200 records at once. This is true regardless of whether or not you believe the tool will only pass records to the trigger individually; all that is required is for an enterprising administrator to create a flow that manipulates multiple records that fire your trigger, and you will have issues.

Consider the following code block, wherein we are looping through each contact we have been provided in a contact trigger and retrieving the related account record, including some information:

trigger ContactTrigger on Contact (before insert, after insert) {
    switch on Trigger.operationType {
        when BEFORE_INSERT {
			for(Contact con : Trigger.new) {
                  Account acc = [SELECT UpsellOpportunity__c                    FROM Account WHERE Id = :con.AccountId];
                      con.Contact_for_Upsell__c = acc.                      UpsellOpportunity__c != 'No';
		    }
		} 
		when AFTER_INSERT {
			//after insert code
		}
	}
}

This simple trigger will set the Contact_for_Upsell__c field to true if the account is marked as having any upsell opportunity.

There are a couple of fairly obvious problems with the way we are querying here. Firstly, this is not bulkified—if we have 200 records passed into the trigger (over 100 records, in fact), we will break the governor limit for Salesforce Object Query Language (SOQL) queries and receive an exception that we cannot handle. Secondly, this setup is also inefficient as it may retrieve the same account record from the database twice.

A better way to manage this would be to gather all of the account IDs in a set and then query once. Not only will this avoid the governor limit—it will also avoid us querying for duplicate results. An updated version of the code to do this is shown here:

trigger ContactTrigger on Contact (before insert, after insert) {
	switch on Trigger.operationType {
        when BEFORE_INSERT {
    
		    Set<Id> accountIds = new Set<Id>();
		    
		    for(Contact con : Trigger.new) {
				accountIds.add(con.AccountId);
		    }
		    
              Map<Id, Account> accountMap = new Map<Id,               Account>([SELECT UpsellOpportunity__c                FROM Account WHERE Id in :accountIds]);
		    
			for(Contact con : Trigger.new) {
                      con.Contact_for_Upsell__c = accountMap.                      get(con.AccountId).UpsellOpportunity__c                       != 'No';
		    }    
		}
		when AFTER_INSERT {
			//after insert code
		}
	}
}

In this code, we declare a Set<Id> called accountIds to hold the account ID for each contact without duplicates. We then query our accounts into a Map<Id, Account> so that when looping through each contact for a second time we can set the value correctly.

Some of you may now be wondering if we have merely moved our performance issue from having too many queries to having multiple loops through all the data. In Chapter 14, Performance Profiling, when we talk about performance profiling, we will cover the use of big-O notation in detail when discussing scaling. However, to touch on the subject of scaling here, it is worth doing some rudimentary analysis. Looping through these records (maximum 200) will be extremely quick on the central processing unit (CPU) and is an inexpensive operation. It is also an operation that scales linearly as the number of records within the trigger grows. In our original trigger, for each new record we had the following:

  • One loop iteration
  • One query

This is scaled linearly at a rate of 1x for both resources—that is, doubling the items doubled the resources being utilized, until a point of failure with a governor limit (in this instance, queries). In our new trigger structure, we have the following for each record:

  • Two loop iterations (one for each for loop)
  • Zero additional queries

Our new resource usage scales linearly for loop iterations but is constant for queries, which are a more limited resource. As we will see later, this is the type of optimization we want within our code. It is therefore imperative that whenever we are looping through records and wish to query related data, we do so in a bulkified manner that, wherever possible, performs a single query for the entire loop.

Bulkification – DML within loops

Similar to the issue of querying in loops is that of performing DML within loops. The limit for DML statements is higher than that of SOQL queries at the time of writing and so is unlikely to present itself as early; however, it follows the same root cause and also the solution.

Take the following code example, in which we are now in the after insert context for our trigger:

trigger ContactTrigger on Contact (before insert) {
	switch on Trigger.operationType {
        when BEFORE_INSERT {
    		//previous trigger code  
		}
		when AFTER_INSERT {
			for(Contact con : Trigger.new) {
                if(con.Contact_for_Upsell__c) {
                    Task t = new Task();
                    t.Subject = 'Discuss opportunities with new                     contact';
                    t.OwnerId = con.OwnerId;
                    t.WhoId = con.Id;
                    insert t;
                }
		    } 
		}
	}
}

Here, we are creating a task for the owner of any new contact that is marked for upsell to contact them and discuss potential opportunities. In our worst-case bulk scenario here, we have 200 contacts that all have the Contact_for_Upsell__c checkbox checked. This will lead to each iteration firing a DML statement that will cause a governor limit exception on record 151. Again, using our rudimentary analysis, we can see that for each additional record on the trigger, we have an additional DML statement that scales linearly until we breach our limit.

Instead, whenever making DML statements (particularly in triggers), we should ensure that we are using the bulk format and passing lists of records to be manipulated into the statement. For example, the trigger code should be written as follows:

trigger ContactTrigger on Contact (before insert) {
	switch on Trigger.operationType {
        when BEFORE_INSERT {
    		//previous trigger code  
		}
		when AFTER_INSERT {
                List<Task> tasks = new List<Task>();
                for(Contact con : Trigger.new) {
                  if(con.Contact_for_Upsell__c) {
                    Task t = new Task();
                    t.Subject = 'Discuss opportunities with new                     contact';
                    t.OwnerId = con.OwnerId;
                    t.WhoId = con.Id;
                    tasks.add(t);
                }
		    }
              insert tasks;
		}
	}
}

This new code has a constant usage of DML statements, one for the entire operation, and can happily scale up to 200 records.

You have been reading a chapter from
Mastering Apex Programming
Published in: Nov 2020
Publisher: Packt
ISBN-13: 9781800200920
Register for a free Packt account to unlock a world of extra content!
A free Packt account unlocks extra newsletters, articles, discounted offers, and much more. Start advancing your knowledge today.
Unlock this book and the full library FREE for 7 days
Get unlimited access to 7000+ expert-authored eBooks and videos courses covering every tech area you can think of
Renews at $19.99/month. Cancel anytime
Banner background image