top of page

Considerations whilst writing Apex or Triggers

Updated: Jun 13, 2020

Refer to 3-forcedotcom-apex-for-developers-page:83-85

Logic should contain as little code as possible.

A single trigger for each object that includes all events.

Trigger.new context variable contains the new sObject record versions and is available in insert, update and undelete.

Trigger.old context variable contains the old sObject record versions and is available in update and delete.

Use Limits.getDMLRows() to count the number of records that have been processed

and Limits.geLimittDMLRows() to return the actual limit. This will help in preventing the roll back of updated transactions due to limit exception.

Eg:

if(Limits.getDMLRows() < Limits.getLimitDMLRows())

insert acct;

Do not use DML inside loops. Build up list of records in a collection and then update outside the loop.

DML limits share the pool of available limits

When making a callout from a trigger, the callout must be done asynchronously so that the trigger process doesn’t block you from working while waiting for the external service's response.

Apex triggers are optimized to operate in bulk. Sets and Maps are used for bulk processing of records.

trigger SoqlTriggerBulk on Account(after update) { // Perform SOQL query once. // Get the accounts and their related opportunities. List<Account> acctsWithOpps = [SELECT Id,(SELECT Id,Name,CloseDate FROM Opportunities) FROM Account WHERE Id IN :Trigger.New]; // Iterate over the returned accounts for(Account a : acctsWithOpps) { Opportunity[] relatedOpps = a.Opportunities; // Do some other processing } }

trigger SoqlTriggerBulk on Account(after update) { // Perform SOQL query once. // Get the related opportunities for the accounts in this trigger, // and iterate over those records. for(Opportunity opp : [SELECT Id,Name,CloseDate FROM Opportunity WHERE AccountId IN :Trigger.New]) { // Do some other processing } }


Also the SOQL for loop can be used to query results that return many records, in this iteration, each loop will process records in batches of 200.


for(List<Opportunity> opps : [select name, desc from Opportunity]){

for(Opportunity o : opps){

o.desc = 'updated';

}

if(Limits.getDMLStatements() < Limits.getLimitDmlStatements(){

update opps;

}

else{

break;


}

}


Combine multiple queries of the same object.


Performing Bulk DML

trigger DmlTriggerBulk on Account(after update) { // Get the related opportunities for the accounts in this trigger. List<Opportunity> relatedOpps = [SELECT Id,Name,Probability FROM Opportunity WHERE AccountId IN :Trigger.New]; List<Opportunity> oppsToUpdate = new List<Opportunity>(); // Iterate over the related opportunities for(Opportunity opp : relatedOpps) { // Update the description when probability is greater // than 50% but less than 100% if ((opp.Probability >= 50) && (opp.Probability < 100)) { opp.Description = 'New description for opportunity.'; oppsToUpdate.add(opp); } } // Perform DML on a collection update oppsToUpdate; }

An Apex Trigger should be logic-less and delegate the logic responsibilities to a handler class. Routing logic using if else statements can be used. New functionality is added without modifying the trigger. You can have a handler function for each event within the class.

In Trigger:

if(Trigger.isBefore){if(Trigger.isInsert){handler.func(Trigger.new)}

....

else if(Trigger.isAfter)....

In Handler class:

public void func(List<sObject> so){...code}


Limit number of queries. Run a single query to retrieve all necessary records.

Use multiple filters or relationships.

And limit the number of records by selecting fewest fields and filter records in SOQL statements.


Also avoid multiple for loops by using if statements:

Eg:

trigger trgSingleSOQL on Account (before update) { //extract the related records for update List<Opportunity> oppWonLost = [select id from Opportunity where (stagename = 'Closed Won' or stagename = 'Closed Lost') and accountid in :Trigger.newMap.keySet()]; List<Opportunity> oppsToUpdate = new List<Opportunity>(); for(Opportunity o : oppWonLost){ if(o.stagename = 'Closed won'){ //update the field //or call the helperclass with static method and pass the  //object as parameter oppsToUpdate.add(o);         }else if(o.stagename = 'Closed lost'){ //update the field //or call the helperclass with static method and pass the  //object as parameter oppsToUpdate.add(o);         }     } update oppsToUpdate;


To ensure a trigger is called only once, before the trigger code is executed a class with a static method/variable can be called to check and set if the code has already been run as part of the transaction.


An after update trigger cannot change field values on the record that triggered it. It updates related record, not itself.

However it can update another record of its type.


If Re-evaluate Workflow Rules After Field Change is enabled for a field update action, Salesforce re-evaluates all workflow rules on the object if the field update results in a change to the value of the field.

If the field update changes the field’s value, all workflow rules on the associated object are re-evaluated. Any workflow rules whose criteria are met as a result of the field update will be triggered.

  • If any of the triggered workflow rules result in another field update that’s also enabled for workflow rule re-evaluation, a domino effect occurs, and more workflow rules can be re-evaluated as a result of the newly-triggered field update. This cascade of workflow rule re-evaluation and triggering can happen up to five times after the initial field update that started it.

  • Make sure that your workflow rules aren’t set up to create recursive loops. For example, if a field update for Rule1 triggers Rule2, and a field update for Rule2 triggers Rule1, the recursive triggers may cause your organization to exceed its limit for workflow time triggers per hour.

When getting methods are defined in an Apex controller it should not have any side effect i.e. idempotent.

It should not include any logic to increment a variable, write log message or add new record.


Call asynchronous apex methods only once.

To collect unique values from the soql result, use Set as its an unordered collection of elements that do not contain any duplicates.

It is required to specify the access modifier for the outer class.

Inner classes can only be only level deep.


Use the WITH SECURITY_ENFORCED clause to enable field and object level security permissions checking for SOQL SELECT queries in Apex code:

eg,List<Account> act1 = [SELECT Id, (SELECT LastName FROM Contacts)

FROM Account WHERE Name like 'Acme' WITH SECURITY_ENFORCED]


Use the stripInaccessible method to enforce field- and object-level data protection. This method can be used to strip the fields from query and subquery results that the user can’t access. The method can also be used to remove inaccessible sObject fields before DML operations to avoid exceptions.

eg:

This example code removes inaccessible fields from the query result. A display table for campaign data must always show the BudgetedCost. The ActualCost must be shown only to users who have permission to read that field.

Security.SObjectAccessDecision securityDecision =

Security.stripInaccessible(AccessType.READABLE,

[SELECT Name, BudgetedCost, ActualCost FROM Campaign];

);


// Construct the output table

if (securityDecision.getRemovedFields().get('Campaign').contains('ActualCost')) {

for (Campaign c : securityDecision.getRecords()) {

//System.debug Output: Name, BudgetedCost

}

} else {

for (Campaign c : securityDecision.getRecords()) {

//System.debug Output: Name, BudgetedCost, ActualCost

}

}

This example code removes inaccessible fields from the subquery result. The user who doesn’t have permission to read the Phone field of a Contacts object.

List<Account> accountsWithContacts =

[SELECT Id, Name, Phone,

(SELECT Id, LastName, Phone FROM Account.Contacts)

FROM Account];

// Strip fields that are not readable

SObjectAccessDecision decision = Security.stripInaccessible(

AccessType.READABLE,

accountsWithContacts);

// Print stripped records

for (Integer i = 0; i < accountsWithContacts.size(); i++)

{

//list records with the stripped field

System.debug('Insecure record access: '+accountsWithContacts[i]);

//list records without the field that is stripped i.e. the field is inaccessible to the current user

System.debug('Secure record access: '+decision.getRecords()[i]);

}

// Print removed fields

System.debug('Fields removed by stripInaccessible: '+decision.getRemovedFields());



Use case: To perform data maintenance that could result in hundreds of records. If a Apex runs a SOQL query that potentially returns more than 50,000 records, it will encounter the governor limit of max. 50,000 records at a time.

And you can only process a total number of 10,000 records in a single Apex transaction.


Solution:

Use Apex batch.

Only use Batch Apex if you have more than one batch of records.

Tune any SOQL query to gather the records to execute as quickly as possible.


Best practices for running within the Apex heap size

  • Don't use class level variables to store a large amounts of data.

  • Utilize SOQL For Loops to iterate and process data from large queries.

  • Construct methods and loops that allow variables to go out of scope as soon as they are no longer needed.

public with sharing class myClass{ private String myString; public myClass(String searchString) { myString = searchString; } private string getQueryString() { return 'SELECT Id, Name FROM Account LIMIT 50000'; } public List<Account> getSearchResults() { List<Account> searchResults = new List<Account>(); for(List<Account> accts : Database.Query(getQueryString())) { // Each loop processes 200 items for(Account a : accts) { if (a.Name != null && a.Name.contains(myString)) { searchResults.add(a); } } } return searchResults; } }


Use caching to improve efficiency:

Use static variables to control execution of your application, and how to use them to cache data that's available through the life of an execution context.

public class UserInfoClass2 {

private static User CachedUserInfo = null;

private User GetUserInfo()

{

if(CachedUserInfo==null)

{

List<User> users = [Select Phone, Extension, MobilePhonefrom User where ID = :UserInfo.getUserId()];

CachedUserInfo = users[0];

}

return CachedUserInfo;

}

public String getPhone()

{

User u = GetUserInfo();

return u.Phone + ( (u.Extension != null)? ' x' + u.Extension : '');

}

public String getMobilePhone()

{

return GetUserInfo().MobilePhone;

}


Do proper design before coding - check the limit usages on the basis of number of records to process at a given time.

Check limits on number of rows that will be returned from a soql query.

Check CPU time used.

Possibility of using aggregate soql.

Possibility of using formula fields/work flows to restrict number of soql query.

Its a good practice to have a class that represents the data structure for returning the result


Prototype to verify the design then build


While design conceptualize using a pseudocode. Come up with multiple designs and check the limit usage - Number of SOQL, Number of rows per SOQL and CPU usage for each design and then select the most efficient one.


You can monitor the amount of CPU time used, and if you see yourself getting close to the limit, you can defer some of theoperations by, for example, launching afuture call to complete the task at hand.

Eg:

To determine total limits available in this execution context:

Integer availableCPUTime = Limits.getLimitCPUTime();

To determine your current usage in this execution context:

Integer usedCPUTime = Limits.getCPUTime();


If you have more than 200,000 records of a given type in a database.Queries in this case must be what they call selective. That means that they must contain a filter condition on an indexed field that reduces the number of records that are looked at. Index fields include certain date fields, the name field,external Id fields, and fields that define relations to other records in the database.


If you are developing for a single org consider using formula, process builder and flow.

Only if you are developing a package for distribution, consider Apex code first.


Only use primitives in SET collection.

for detecting or preventing duplicate Id's.

DML operations that will fail if you have duplicate objects in a list.

They can be used in queries in the filter term where they support both the inand not in filter operators.

A set can be passed as a constructor to a list, so it's very easy to take a set of elements and convert them into a list. Sets include an addAll method to quickly add all items in a list to a set offering a fast way to remove duplicate elements from a list.


Map

You can also use the values method of the map to obtain a list of the objects for a DML operation. The beautiful thing about this is that because the keys are unique, as long as you're using the Id field as the key, you are guaranteed not to have any duplicate objects in the DML statement. This is so useful that it's not uncommon to create a map just to hold the collection of those objects that you've modified in a complex set of code. As you go through the code, every time you make a modification, just go ahead and put that object into the map. It doesn't matter if you put the object into the map multiple times. Each time you do it will replace the previous entry. Since you are essentially adding the same object over and over, this allows you to efficiently build the collection of those objects that have been modified while ensuring that no object appears in the list twice.

Eg:

List<Contact> contacts = …

Map<ID,Contact> updated = new Map<ID,Contact>();

For(Contact c: contacts) {

// If it’s updated

updated.put(c.id, c);

}

Update updated.values();


Use defensive Apex coding:

Its about thinking defensively while writing code, for eg, what if the code is already in the future context, what other options I have, can I call a synchronous call etc.

So you will be using a lot of if() construct in your coding.


public class apxDefensiveCode {

public void callFutureMethod(){

if(System.isFuture() || System.isBatch()){

defensiveFutureCallSync();

}

else{

if(Limits.getFutureCalls() < Limits.getLimitFutureCalls()){

futureMethod();

}

else{//do a synchronous call or queuable call}

}

}

public static void defensiveFutureCallSync(){ //synchronous code}

@future

public static void futureMethod(){}


}

Note: In Trigger.OldMap the old value is the value that existed in the beginning of the execution context so even if the value changes down the line within the execution context, it is going to consider the value at the beginning of the execution context.


Another example of defensive coding to ensure that the trigger executes only once:

//update the counter field in Opportunity

//when the stage is moved from prospecting to qualification

trigger apxOppStageName on Opportunity (before Update) {

apexDefensiveCode.defensiveOppTrig(Trigger.New, Trigger.OldMap);


}

public class apxDefensiveCode {

public static void defensiveOppTrig(List<Opportunity> newValue, Map<id,Opportunity> oldMap){

//to old the prior stagename

Map<id,String> priorValueMap = new Map<id,String>();

for(Opportunity o : newValue){

//the first time it executes there will not be a key

//so it will return the old value at the begining of the execution context

String priorValue = priorValueMap.containsKey(o.id) ? priorValueMap.get(o.id) : oldMap.get(o.id).StageName;

if(o.StageName != 'Prospecting' && priorValue == 'Prospecting'){

//update the counter field in opportunity

}

//this map will have the new stagename

//so again if the control tries to execute the code

//priorValue == 'Prospecting' condition will fail

//and the code will not be executed

//note that the defense mechanism can be done also

//using a static boolean variable

//but this one particulary targets changes to stagename

priorValueMap.put(o.id, o.StageName);

}

}


}


public class apxDefensiveCode {

public static void updateRec(){

List<Database.SaveResult> dmlResults = Database.update(listOfRecords, false);

for(Integer ctr=0; ctr < ops.size(),ctr++){

Database.SaveResult sr = dmlResults[ctr];

if(!sr.isSuccess())

for(Database.Error err : sr.getErrors()){

if(err.getStatusCode() == StatusCode.UNABLE_TO_LOCK_ROW){

//Maybe use a asynchronous call to update that record again

}

}

}

}

}

}


Use Apex Test Tracker

This application automatically evaluates and runs unit tests on your organization as scheduled, and performs user defined actions (such as Email notifications) when a test fails that was not previously failing. This can be used by org administrators to receive early notification that a metadata change has caused a unit test to start failing. It can also be used by developers as a light weight continuous integration tool.

The Apex Test Tracker application is now available on the AppExchange as a free app!

Recent Posts

See All
Governor Limits

SOQL queries: 100 Number of SOQL results: 50000 SOSL queries: 20 DML Statements: 150 Number of DML results: 10000

 
 
 

Comments


Post: Blog2_Post

©2020 by SalesforceDemystified. Proudly created with Wix.com

bottom of page