Salesforce Triggers

Ace your homework & exams now with Quizwiz!

Context Variable Considerations

Be aware of the following considerations for trigger context variables: trigger.new and trigger.old cannot be used in Apex DML operations. You can use an object to change its own field values using trigger.new, but only in before triggers. In all after triggers, trigger.new is not saved, so a runtime exception is thrown. trigger.old is always read-only. You cannot delete trigger.new. The following table lists considerations about certain actions in different trigger events: Trigger Event Can change fields using trigger.new Can update original object using an update DML operation Can delete original object using a delete DML operation before insert Allowed. Not applicable. The original object has not been created; nothing can reference it, so nothing can update it. Not applicable. The original object has not been created; nothing can reference it, so nothing can update it. after insert Not allowed. A runtime error is thrown, as trigger.new is already saved. Allowed. Allowed, but unnecessary. The object is deleted immediately after being inserted. before update Allowed. Not allowed. A runtime error is thrown. Not allowed. A runtime error is thrown. after update Not allowed. A runtime error is thrown, as trigger.new is already saved. Allowed. Even though bad code could cause an infinite recursion doing this incorrectly, the error would be found by the governor limits. Allowed. The updates are saved before the object is deleted, so if the object is undeleted, the updates become visible. before delete Not allowed. A runtime error is thrown. trigger.new is not available in before delete triggers. Allowed. The updates are saved before the object is deleted, so if the object is undeleted, the updates become visible. Not allowed. A runtime error is thrown. The deletion is already in progress. after delete Not allowed. A runtime error is thrown. trigger.new is not available in after delete triggers. Not applicable. The object has already been deleted. Not applicable. The object has already been deleted. after undelete Not allowed. A runtime error is thrown. Allowed. Allowed, but unnecessary. The object is deleted immediately after being inserted.

Trigger and Bulk Request Best Practices

A common development pitfall is the assumption that trigger invocations never include more than one record. Apex triggers are optimized to operate in bulk, which, by definition, requires developers to write logic that supports bulk operations. This is an example of a flawed programming pattern. It assumes that only one record is pulled in during a trigger invocation. While this might support most user interface events, it does not support bulk operations invoked through the SOAP API or Visualforce. 1 trigger MileageTrigger on Mileage__c (before insert, before update) { 2 User c = [SELECT Id FROM User WHERE mileageid__c = Trigger.new[0].id]; 3 } This is another example of a flawed programming pattern. It assumes that less than 100 records are pulled in during a trigger invocation. If more than 20 records are pulled into this request, the trigger would exceed the SOQL query limit of 100 SELECT statements: 1 trigger MileageTrigger on Mileage__c (before insert, before update) { 2 for(mileage__c m : Trigger.new){ 3 User c = [SELECT Id FROM user WHERE mileageid__c = m.Id]; 4 } 5 } For more information on governor limits, see Execution Governors and Limits. This example demonstrates the correct pattern to support the bulk nature of triggers while respecting the governor limits: 1 Trigger MileageTrigger on Mileage__c (before insert, before update) { 2 Set<ID> ids = Trigger.newMap.keySet(); 3 List<User> c = [SELECT Id FROM user WHERE mileageid__c in :ids]; 4 } This pattern respects the bulk nature of the trigger by passing the Trigger.new collection to a set, then using the set in a single SOQL query. This pattern captures all incoming records within the request while limiting the number of SOQL queries. Best Practices for Designing Bulk Programs The following are the best practices for this design pattern: Minimize the number of data manipulation language (DML) operations by adding records to collections and performing DML operations against these collections. Minimize the number of SOQL statements by preprocessing records and generating sets, which can be placed in single SOQL statement used with the IN clause. See Also: Developing Code in the Cloud

Bulk Triggers

All triggers are bulk triggers by default, and can process multiple records at a time. You should always plan on processing more than one record at a time. Note An Event object that is defined as recurring is not processed in bulk for insert, delete, or update triggers. Bulk triggers can handle both single record updates and bulk operations like: Data import Force.com Bulk API calls Mass actions, such as record owner changes and deletes Recursive Apex methods and triggers that invoke bulk DML statements

Trigger Context Variables

All triggers define implicit variables that allow developers to access run-time context. These variables are contained in the System.Trigger class. Variable Usage isExecuting Returns true if the current context for the Apex code is a trigger, not a Visualforce page, a Web service, or an executeanonymous() API call. isInsert Returns true if this trigger was fired due to an insert operation, from the Salesforce user interface, Apex, or the API. isUpdate Returns true if this trigger was fired due to an update operation, from the Salesforce user interface, Apex, or the API. isDelete Returns true if this trigger was fired due to a delete operation, from the Salesforce user interface, Apex, or the API. isBefore Returns true if this trigger was fired before any record was saved. isAfter Returns true if this trigger was fired after all records were saved. isUndelete Returns true if this trigger was fired after a record is recovered from the Recycle Bin (that is, after an undelete operation from the Salesforce user interface, Apex, or the API.) new Returns a list of the new versions of the sObject records. This sObject list is only available in insert, update, and undelete triggers, and the records can only be modified in before triggers. newMap A map of IDs to the new versions of the sObject records. This map is only available in before update, after insert, after update, and after undelete triggers. old Returns a list of the old versions of the sObject records. This sObject list is only available in update and delete triggers. oldMap A map of IDs to the old versions of the sObject records. This map is only available in update and delete triggers. size The total number of records in a trigger invocation, both old and new. Note If any record that fires a trigger includes an invalid field value (for example, a formula that divides by zero), that value is set to null in the new, newMap, old, and oldMap trigger context variables. For example, in this simple trigger, Trigger.new is a list of sObjects and can be iterated over in a for loop, or used as a bind variable in the IN clause of a SOQL query. Trigger simpleTrigger on Account (after insert) { for (Account a : Trigger.new) { // Iterate over each sObject } // This single query finds every contact that is associated with any of the // triggering accounts. Note that although Trigger.new is a collection of // records, when used as a bind variable in a SOQL query, Apex automatically // transforms the list of records into a list of corresponding Ids. Contact[] cons = [SELECT LastName FROM Contact WHERE AccountId IN :Trigger.new]; } This trigger uses Boolean context variables like Trigger.isBefore and Trigger.isDelete to define code that only executes for specific trigger conditions: trigger myAccountTrigger on Account(before delete, before insert, before update, after delete, after insert, after update) { if (Trigger.isBefore) { if (Trigger.isDelete) { // In a before delete trigger, the trigger accesses the records that will be // deleted with the Trigger.old list. for (Account a : Trigger.old) { if (a.name != 'okToDelete') { a.addError('You can\'t delete this record!'); } } } else { // In before insert or before update triggers, the trigger accesses the new records // with the Trigger.new list. for (Account a : Trigger.new) { if (a.name == 'bad') { a.name.addError('Bad name'); } } if (Trigger.isInsert) { for (Account a : Trigger.new) { System.assertEquals('xxx', a.accountNumber); System.assertEquals('industry', a.industry); System.assertEquals(100, a.numberofemployees); System.assertEquals(100.0, a.annualrevenue); a.accountNumber = 'yyy'; } // If the trigger is not a before trigger, it must be an after trigger. } else { if (Trigger.isInsert) { List<Contact> contacts = new List<Contact>(); for (Account a : Trigger.new) { if(a.Name == 'makeContact') { contacts.add(new Contact (LastName = a.Name, AccountId = a.Id)); } } insert contacts; } } }}}

Common Bulk Trigger Idioms

Although bulk triggers allow developers to process more records without exceeding execution governor limits, they can be more difficult for developers to understand and code because they involve processing batches of several records at a time. The following sections provide examples of idioms that should be used frequently when writing in bulk. Using Maps and Sets in Bulk Triggers Set and map data structures are critical for successful coding of bulk triggers. Sets can be used to isolate distinct records, while maps can be used to hold query results organized by record ID. For example, this bulk trigger from the sample quoting application first adds each pricebook entry associated with the OpportunityLineItem records in Trigger.new to a set, ensuring that the set contains only distinct elements. It then queries the PricebookEntries for their associated product color, and places the results in a map. Once the map is created, the trigger iterates through the OpportunityLineItems in Trigger.new and uses the map to assign the appropriate color. 01 // When a new line item is added to an opportunity, this trigger copies the value of the 02 // associated product's color to the new record. 03 trigger oppLineTrigger on OpportunityLineItem (before insert) { 04 05 // For every OpportunityLineItem record, add its associated pricebook entry 06 // to a set so there are no duplicates. 07 Set<Id> pbeIds = new Set<Id>(); 08 for (OpportunityLineItem oli : Trigger.new) 09 pbeIds.add(oli.pricebookentryid); 10 11 // Query the PricebookEntries for their associated product color and place the results 12 // in a map. 13 Map<Id, PricebookEntry> entries = new Map<Id, PricebookEntry>( 14 [select product2.color__c from pricebookentry 15 where id in :pbeIds]); 16 17 // Now use the map to set the appropriate color on every OpportunityLineItem processed 18 // by the trigger. 19 for (OpportunityLineItem oli : Trigger.new) 20 oli.color__c = entries.get(oli.pricebookEntryId).product2.color__c; 21 } Correlating Records with Query Results in Bulk Triggers Use the Trigger.newMap and Trigger.oldMap ID-to-sObject maps to correlate records with query results. For example, this trigger from the sample quoting app uses Trigger.oldMap to create a set of unique IDs (Trigger.oldMap.keySet()). The set is then used as part of a query to create a list of quotes associated with the opportunities being processed by the trigger. For every quote returned by the query, the related opportunity is retrieved from Trigger.oldMap and prevented from being deleted: 1 trigger oppTrigger on Opportunity (before delete) { 2 for (Quote__c q : [SELECT opportunity__c FROM quote__c 3 WHERE opportunity__c IN :Trigger.oldMap.keySet()]) { 4 Trigger.oldMap.get(q.opportunity__c).addError('Cannot delete 5 opportunity with a quote'); 6 } 7 } Using Triggers to Insert or Update Records with Unique Fields When an insert or upsert event causes a record to duplicate the value of a unique field in another new record in that batch, the error message for the duplicate record includes the ID of the first record. However, it is possible that the error message may not be correct by the time the request is finished. When there are triggers present, the retry logic in bulk operations causes a rollback/retry cycle to occur. That retry cycle assigns new keys to the new records. For example, if two records are inserted with the same value for a unique field, and you also have an insert event defined for a trigger, the second duplicate record fails, reporting the ID of the first record. However, once the system rolls back the changes and re-inserts the first record by itself, the record receives a new ID. That means the error message reported by the second record is no longer valid. Previous

Triggers and Callouts

Apex allows you to make calls to and integrate your Apex code with external Web services. Apex calls to external Web services are referred to as callouts. For example, you can make a callout to a stock quote service to get the latest quotes. 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.The asynchronous callout is made in a background process, and the response is received when the external service returns it. To make a callout from a trigger, call a class method that executes asynchronously. Such a method is called a future method and is annotated with @future(callout=true). This example class contains the future method that makes the callout. Note The example uses a hypothetical endpoint URL for illustration purposes only. You can't run this example unless you change the endpoint to a valid URL and add a remote site in Salesforce for your endpoint.

Triggers

Apex can be invoked by using triggers. Apex triggers enable you to perform custom actions before or after changes to Salesforce records, such as insertions, updates, or deletions. A trigger is Apex code that executes before or after the following types of operations: insert update delete merge upsert undelete For example, you can have a trigger run before an object's records are inserted into the database, after records have been deleted, or even after a record is restored from the Recycle Bin. You can define triggers for top-level standard objects that support triggers, such as a Contact or an Account, some standard child objects, such as a CaseComment, and custom objects. To define a trigger, from the object management settings for the object whose triggers you want to access, go to Triggers. There are two types of triggers: Before triggers are used to update or validate record values before they're saved to the database. After triggers are used to access field values that are set by the system (such as a record's Id or LastModifiedDate field), and to affect changes in other records, such as logging into an audit table or firing asynchronous events with a queue. The records that fire the after trigger are read-only. Triggers can also modify other records of the same type as the records that initially fired the trigger. For example, if a trigger fires after an update of contact A, the trigger can also modify contacts B, C, and D. Because triggers can cause other records to change, and because these changes can, in turn, fire more triggers, the Apex runtime engine considers all such operations a single unit of work and sets limits on the number of operations that can be performed to prevent infinite recursion. See Execution Governors and Limits. Additionally, if you update or delete a record in its before trigger, or delete a record in its after trigger, you will receive a runtime error. This includes both direct and indirect operations. For example, if you update account A, and the before update trigger of account A inserts contact B, and the after insert trigger of contact B queries for account A and updates it using the DML update statement or database method, then you are indirectly updating account A in its before trigger, and you will receive a runtime error. Implementation Considerations Before creating triggers, consider the following: upsert triggers fire both before and after insert or before and after update triggers as appropriate. merge triggers fire both before and after delete for the losing records, and both before and after update triggers for the winning record. See Triggers and Merge Statements. Triggers that execute after a record has been undeleted only work with specific objects. See Triggers and Recovered Records. Field history is not recorded until the end of a trigger. If you query field history in a trigger, you don't see any history for the current transaction. Field history tracking honors the permissions of the current user. If the current user doesn't have permission to directly edit an object or field, but the user activates a trigger that changes an object or field with history tracking enabled, no history of the change is recorded. Callouts must be made asynchronously from a trigger so that the trigger process isn't blocked while waiting for the external service's response. The asynchronous callout is made in a background process, and the response is received when the external service returns it. To make an asynchronous callout, use asynchronous Apex such as a future method. See Invoking Callouts Using Apex for more information. In API version 20.0 and earlier, if a Bulk API request causes a trigger to fire, each chunk of 200 records for the trigger to process is split into chunks of 100 records. In Salesforce API version 21.0 and later, no further splits of API chunks occur. If a Bulk API request causes a trigger to fire multiple times for chunks of 200 records, governor limits are reset between these trigger invocations for the same HTTP request. Bulk Triggers Trigger Syntax Trigger Context Variables Context Variable Considerations Common Bulk Trigger Idioms Defining Triggers Triggers and Merge Statements Triggers and Recovered Records Triggers and Order of Execution Operations That Don't Invoke Triggers Some operations don't invoke triggers. Entity and Field Considerations in Triggers When you create triggers, consider the behavior of certain entities, fields, and operations. Triggers for Chatter Objects You can write triggers for the FeedItem and FeedComment objects. Trigger Exceptions Trigger and Bulk Request Best Practices

What can Apex triggers do?

Apex triggers enable you to perform custom actions before or after events to records in Salesforce, such as insertions, updates, or deletions. Just like database systems support triggers, Apex provides trigger support for managing records.

Beyond the basics

Calling addError() in a trigger causes the entire set of operations to roll back, except when bulk DML is called with partial success. If a DML statement in Apex spawned the trigger, any error rolls back the entire operation. However, the runtime engine still processes every record in the operation to compile a comprehensive list of errors. If a bulk DML call in the Force.com API spawned the trigger, the runtime engine sets the bad records aside. The runtime engine then attempts a partial save of the records that did not generate errors.

How do you call a static method in a trigger?

In the Developer Console, click File | New | Apex Trigger. Enter ExampleTrigger for the trigger name, and then select Contact for the sObject. Click Submit. Replace the default code with the following, and then modify the email address placeholder text in sendMail() to your email address. trigger ExampleTrigger on Contact (after insert, after delete) { if (Trigger.isInsert) { Integer recordCount = Trigger.New.size(); // Call a utility method from another class EmailManager.sendMail('Your email address', 'Trailhead Trigger Tutorial', recordCount + ' contact(s) were inserted.'); } else if (Trigger.isDelete) { // Process after delete } } To save, press Ctrl+S. To test the trigger, create a contact. Click Debug | Open Execute Anonymous Window. In the new window, add the following and then click Execute. Contact c = new Contact(LastName='Test Contact'); insert c; In the debug log, check that the trigger was fired. Toward the end of the log, find the debug message that was written by the utility method: DEBUG|Email sent successfully Now check that you received an email with the body text 1 contact(s) were inserted. With your new trigger in place, you get an email every time you add one or more contacts!

Triggers and Merge Statements

Merge events do not fire their own trigger events. Instead, they fire delete and update events as follows: Deletion of losing records A single merge operation fires a single delete event for all records that are deleted in the merge. To determine which records were deleted as a result of a merge operation use the MasterRecordId field in Trigger.old. When a record is deleted after losing a merge operation, its MasterRecordId field is set to the ID of the winning record. The MasterRecordId field is only set in after delete trigger events. If your application requires special handling for deleted records that occur as a result of a merge, you need to use the after delete trigger event. Update of the winning record A single merge operation fires a single update event for the winning record only. Any child records that are reparented as a result of the merge operation do not fire triggers. For example, if two contacts are merged, only the delete and update contact triggers fire. No triggers for records related to the contacts, such as accounts or opportunities, fire. The following is the order of events when a merge occurs: The before delete trigger fires. The system deletes the necessary records due to the merge, assigns new parent records to the child records, and sets the MasterRecordId field on the deleted records. The after delete trigger fires. The system does the specific updates required for the master record. Normal update triggers apply.

Operations That Don't Invoke Triggers

Some operations don't invoke triggers. Triggers are invoked for data manipulation language (DML) operations that are initiated or processed by the Java application server. Therefore, some system bulk operations don't invoke triggers. Some examples include: Cascading delete operations. Records that did not initiate a delete don't cause trigger evaluation. Cascading updates of child records that are reparented as a result of a merge operation Mass campaign status changes Mass division transfers Mass address updates Mass approval request transfers Mass email actions Modifying custom field data types Renaming or replacing picklists Managing price books Changing a user's default division with the transfer division option checked Changes to the following objects: BrandTemplate MassEmailTemplate Folder Update account triggers don't fire before or after a business account record type is changed to person account (or a person account record type is changed to business account.) Note Inserts, updates, and deletes on person accounts fire Account triggers, not Contact triggers. The before triggers associated with the following operations are fired during lead conversion only if validation and triggers for lead conversion are enabled in the organization: insert of accounts, contacts, and opportunities update of accounts and contacts Opportunity triggers are not fired when the account owner changes as a result of the associated opportunity's owner changing. When you modify an opportunity product on an opportunity, or when an opportunity product schedule changes an opportunity product, even if the opportunity product changes the opportunity, the before and after triggers and the validation rules don't fire for the opportunity. However, roll-up summary fields do get updated, and workflow rules associated with the opportunity do run. The getContent and getContentAsPDF PageReference methods aren't allowed in triggers. Note the following for the ContentVersion object: Content pack operations involving the ContentVersion object, including slides and slide autorevision, don't invoke triggers. Note Content packs are revised when a slide inside the pack is revised. Values for the TagCsv and VersionData fields are only available in triggers if the request to create or update ContentVersion records originates from the API. You can't use before or after delete triggers with the ContentVersion object. Triggers on the Attachment object don't fire when: the attachment is created via Case Feed publisher. the user sends email via the Email related list and adds an attachment file. Triggers fire when the Attachment object is created via Email-to-Case or via the UI.

Triggers and Recovered Records

The after undelete trigger event only works with recovered records—that is, records that were deleted and then recovered from the Recycle Bin through the undelete DML statement. These are also called undeleted records. The after undelete trigger events only run on top-level objects. For example, if you delete an Account, an Opportunity may also be deleted. When you recover the Account from the Recycle Bin, the Opportunity is also recovered. If there is an after undelete trigger event associated with both the Account and the Opportunity, only the Account after undelete trigger event executes. The after undelete trigger event only fires for the following objects: Account Asset Campaign Case Contact ContentDocument Contract Custom objects Event Lead Opportunity Product Solution Task

What is the syntax of a trigger?

The syntax of a trigger definition is different from a class definition's syntax. A trigger definition starts with the trigger keyword. It is then followed by the name of the trigger, the Salesforce object that the trigger is associated with, and the conditions under which it fires. A trigger has the following syntax: trigger TriggerName on ObjectName (trigger_events) { code_block }

When does the system save the records that fired the before trigger?

The system saves the records that fired the before trigger after the trigger finishes execution. You can modify the records in the trigger without explicitly calling a DML insert or update operation. If you perform DML statements on those records, you get an error.

Beyond the basics

The trigger you've added iterates over all records that are part of the trigger context—the for loop iterates over Trigger.New. However, the loop in this trigger could be more efficient. We don't really need to access every account in this trigger context, but only a subset—the accounts without opportunities. The next unit shows how to make this trigger more efficient. In the Bulk Trigger Design Patterns unit, learn how to modify the SOQL query to get only the accounts with no opportunities. Then, learn to iterate only over those records.

Types of Triggers

There are two types of triggers. Before triggers are used to update or validate record values before they're saved to the database. After triggers are used to access field values that are set by the system (such as a record's Id or LastModifiedDate field), and to affect changes in other records. The records that fire the after trigger are read-only.

Example using context variables in a trigger

This example is a modified version of the HelloWorldTrigger example trigger. It iterates over each account in a for loop and updates the Description field for each. view sourceprint? trigger HelloWorldTrigger on Account (before insert) { for(Account a : Trigger.New) { a.Description = 'New description'; } }

Give an example Trigger

This simple trigger fires before you insert an account and writes a message to the debug log. In the Developer Console, click File | New | Apex Trigger. Enter HelloWorldTrigger for the trigger name, and then select Account for the sObject. Click Submit. Replace the default code with the following. trigger HelloWorldTrigger on Account (before insert) { System.debug('Hello World!'); } To save, press Ctrl+S. To test the trigger, create an account. Click Debug | Open Execute Anonymous Window. In the new window, add the following and then click Execute. Account a = new Account(Name='Test Trigger'); insert a; In the debug log, find the Hello World! statement. The log also shows that the trigger has been executed. Types of Triggers

Using Context Variables

To access the records that caused the trigger to fire, use context variables. For example, Trigger.New contains all the records that were inserted in insert or update triggers. Trigger.Old provides the old version of sObjects before they were updated in update triggers, or a list of deleted sObjects in delete triggers. Triggers can fire when one record is inserted, or when many records are inserted in bulk via the API or Apex. Therefore, context variables, such as Trigger.New, can contain only one record or multiple records. You can iterate over Trigger.New to get each individual sObject.

Trigger Syntax

To define a trigger, use the following syntax: trigger TriggerName on ObjectName (trigger_events) { code_block } where trigger_events can be a comma-separated list of one or more of the following events: before insert before update before delete after insert after update after delete after undelete Note A trigger invoked by an insert, delete, or update of a recurring event or recurring task results in a runtime error when the trigger is called in bulk from the Force.com​ API. For example, the following code defines a trigger for the before insert and before update events on the Account object: trigger myAccountTrigger on Account (before insert, before update) { // Your code here } The code block of a trigger cannot contain the static keyword. Triggers can only contain keywords applicable to an inner class. In addition, you do not have to manually commit any database changes made by a trigger. If your Apex trigger completes successfully, any database changes are automatically committed. If your Apex trigger does not complete successfully, any changes made to the database are rolled back.

How do I execture a trigger? What are the possible events?

To execute a trigger before or after insert, update, delete, and undelete operations, specify multiple trigger events in a comma-separated list. The events you can specify are: before insert before update before delete after insert after update after delete after undelete

Defining Triggers

Trigger code is stored as metadata under the object with which they are associated. To define a trigger in Salesforce: From the object management settings for the object whose triggers you want to access, go to Triggers. Tip For the Attachment, ContentDocument, and Note standard objects, you can't create a trigger in the Salesforce user interface. For these objects, create a trigger using development tools, such as the Developer Console or the Force.com IDE. Alternatively, you can also use the Metadata API. In the Triggers list, click New. Click Version Settings to specify the version of Apex and the API used with this trigger. If your organization has installed managed packages from the AppExchange, you can also specify which version of each managed package to use with this trigger. Use the default values for all versions. This associates the trigger with the most recent version of Apex and the API, as well as each managed package. You can specify an older version of a managed package if you want to access components or functionality that differs from the most recent package version. Click Apex Trigger and select the Is Active checkbox if the trigger should be compiled and enabled. Leave this checkbox deselected if you only want to store the code in your organization's metadata. This checkbox is selected by default. In the Body text box, enter the Apex for the trigger. A single trigger can be up to 1 million characters in length. To define a trigger, use the following syntax: 1 trigger TriggerName on ObjectName (trigger_events) { 2 code_block 3 } where trigger_events can be a comma-separated list of one or more of the following events: before insert before update before delete after insert after update after delete after undelete Note A trigger invoked by an insert, delete, or update of a recurring event or recurring task results in a runtime error when the trigger is called in bulk from the Force.com​ API. Click Save. Note Triggers are stored with an isValid flag that is set to true as long as dependent metadata has not changed since the trigger was last compiled. If any changes are made to object names or fields that are used in the trigger, including superficial changes such as edits to an object or field description, the isValid flag is set to false until the Apex compiler reprocesses the code. Recompiling occurs when the trigger is next executed, or when a user re-saves the trigger in metadata. If a lookup field references a record that has been deleted, Salesforce clears the value of the lookup field by default. Alternatively, you can choose to prevent records from being deleted if they're in a lookup relationship. The Apex Trigger Editor The Apex and Visualforce editor has the following functionality: Syntax highlighting The editor automatically applies syntax highlighting for keywords and all functions and operators. Search (Search icon) Search enables you to search for text within the current page, class, or trigger. To use search, enter a string in the Search textbox and click Find Next. To replace a found search string with another string, enter the new string in the Replace textbox and click replace to replace just that instance, or Replace All to replace that instance and all other instances of the search string that occur in the page, class, or trigger. To make the search operation case sensitive, select the Match Case option. To use a regular expression as your search string, select the Regular Expressions option. The regular expressions follow JavaScript's regular expression rules. A search using regular expressions can find strings that wrap over more than one line. If you use the replace operation with a string found by a regular expression, the replace operation can also bind regular expression group variables ($1, $2, and so on) from the found search string. For example, to replace an <h1> tag with an <h2> tag and keep all the attributes on the original <h1> intact, search for <h1(\s+)(.*)> and replace it with <h2$1$2>. Go to line (Go To Line icon) This button allows you to highlight a specified line number. If the line is not currently visible, the editor scrolls to that line. Undo (Undo icon) and Redo (Redo icon) Use undo to reverse an editing action and redo to recreate an editing action that was undone. Font size Select a font size from the drop-down list to control the size of the characters displayed in the editor. Line and column position The line and column position of the cursor is displayed in the status bar at the bottom of the editor. This can be used with go to line (Go To Line icon) to quickly navigate through the editor. Line and character count The total number of lines and characters is displayed in the status bar at the bottom of the editor.

Adding Related Records

Triggers are often used to access and manage records related to the records in the trigger context—the records that caused this trigger to fire. This trigger adds a related opportunity for each new or updated account if no opportunity is already associated with the account. The trigger first performs a SOQL query to get all child opportunities for the accounts that the trigger fired on. Next, the trigger iterates over the list of sObjects in Trigger.New to get each account sObject. If the account doesn't have any related opportunity sObjects, the for loop creates one. If the trigger created any new opportunities, the final statement inserts them. Add the following trigger using the Developer Console (follow the steps of the HelloWorldTrigger example but use AddRelatedRecord for the trigger name). trigger AddRelatedRecord on Account(after insert, after update) { List<Opportunity> oppList = new List<Opportunity>(); // Get the related opportunities for the accounts in this trigger Map<Id,Account> acctsWithOpps = new Map<Id,Account>( [SELECT Id,(SELECT Id FROM Opportunities) FROM Account WHERE Id IN :Trigger.New]); // Add an opportunity for each account if it doesn't already have one. // Iterate through each account. for(Account a : Trigger.New) { System.debug('acctsWithOpps.get(a.Id).Opportunities.size()=' + acctsWithOpps.get(a.Id).Opportunities.size()); // Check if the account already has a related opportunity. if (acctsWithOpps.get(a.Id).Opportunities.size() == 0) { // If it doesn't, add a default opportunity oppList.add(new Opportunity(Name=a.Name + ' Opportunity', StageName='Prospecting', CloseDate=System.today().addMonths(1), AccountId=a.Id)); } } if (oppList.size() > 0) { insert oppList; } } To test the trigger, create an account in the Salesforce user interface and name it Apples & Oranges. In the Opportunities related list on the account's page, find the new opportunity. The trigger added this opportunity automatically!

What can triggers be defined for? When are they active?

Triggers can be defined for top-level standard objects, such as Account or Contact, custom objects, and some standard child objects. Triggers are active by default when created. Salesforce automatically fires active triggers when the specified database events occur.

Trigger Exceptions

Triggers can be used to prevent DML operations from occurring by calling the addError() method on a record or field. When used on Trigger.new records in insert and update triggers, and on Trigger.old records in delete triggers, the custom error message is displayed in the application interface and logged. Note Users experience less of a delay in response time if errors are added to before triggers. A subset of the records being processed can be marked with the addError() method: If the trigger was spawned by a DML statement in Apex, any one error results in the entire operation rolling back. However, the runtime engine still processes every record in the operation to compile a comprehensive list of errors. If the trigger was spawned by a bulk DML call in the Force.com API, the runtime engine sets aside the bad records and attempts to do a partial save of the records that did not generate errors. See Bulk DML Exception Handling. If a trigger ever throws an unhandled exception, all records are marked with an error and no further processing takes place. See Also: addError(errorMsg) addError(errorMsg)

Why use Apex trigger?

Typically, you use triggers to perform operations based on specific conditions, to modify related records or restrict certain operations from happening. You can use triggers to do anything you can do in Apex, including executing SOQL and DML or calling custom Apex methods.

When would you use a trigger?

Use triggers to perform tasks that can't be done by using the point-and-click tools in the Salesforce user interface. For example, if validating a field value or updating a field on a record, use validation rules and workflow rules instead.

Comprehensive list of all context variables available for triggers.

Variable Usage isExecuting- Returns true if the current context for the Apex code is a trigger, not a Visualforce page, a Web service, or an executeanonymous() API call. isInsert- Returns true if this trigger was fired due to an insert operation, from the Salesforce user interface, Apex, or the API. isUpdate- Returns true if this trigger was fired due to an update operation, from the Salesforce user interface, Apex, or the API. isDelete- Returns true if this trigger was fired due to a delete operation, from the Salesforce user interface, Apex, or the API. isBefore- Returns true if this trigger was fired before any record was saved. isAfter- Returns true if this trigger was fired after all records were saved. isUndelete- Returns true if this trigger was fired after a record is recovered from the Recycle Bin (that is, after an undelete operation from the Salesforce user interface, Apex, or the API.) new Returns a list of the new versions of the sObject records. This sObject list is only available in insert, update, and undelete triggers, and the records can only be modified in before triggers. newMap- A map of IDs to the new versions of the sObject records. This map is only available in before update, after insert, after update, and after undelete triggers. old- Returns a list of the old versions of the sObject records. This sObject list is only available in update and delete triggers. oldMap- A map of IDs to the old versions of the sObject records. This map is only available in update and delete triggers. size- The total number of records in a trigger invocation, both old and new.

Entity and Field Considerations in Triggers

When you create triggers, consider the behavior of certain entities, fields, and operations. QuestionDataCategorySelection Entity Not Available in After Insert Triggers The after insert trigger that fires after inserting one or more Question records doesn't have access to the QuestionDataCategorySelection records that are associated with the inserted Questions. For example, the following query doesn't return any results in an after insert trigger: 1 QuestionDataCategorySelection[] dcList = 2 [select Id,DataCategoryName from QuestionDataCategorySelection where ParentId IN :questions]; Fields Not Updateable in Before Triggers Some field values are set during the system save operation, which occurs after before triggers have fired. As a result, these fields cannot be modified or accurately detected in before insert or before update triggers. Some examples include: Task.isClosed Opportunity.amount* Opportunity.ForecastCategory Opportunity.isWon Opportunity.isClosed Contract.activatedDate Contract.activatedById Case.isClosed Solution.isReviewed Id (for all records)** createdDate (for all records)** lastUpdated (for all records) Event.WhoId (when Shared Activities is enabled) Task.WhoId (when Shared Activities is enabled) * When Opportunity has no lineitems, Amount can be modified by a before trigger. ** Id and createdDate can be detected in before update triggers, but cannot be modified. Fields Not Updateable in After Triggers The following fields can't be updated by after insert or after update triggers. Event.WhoId Task.WhoId Considerations for Event DateTime Fields in Insert and Update Triggers We recommend using the following date and time fields to create or update events. When creating or updating a timed Event, use ActivityDateTime to avoid issues with inconsistent date and time values. When creating or updating an all-day Event, use ActivityDate to avoid issues with inconsistent date and time values. We recommend that you use DurationInMinutes because it works with all updates and creates for Events. Operations Not Supported in Insert and Update Triggers The following operations aren't supported in insert and update triggers. Manipulating an activity relation through the TaskRelation or EventRelation object, if Shared Activities is enabled Manipulating an invitee relation on a group event through the Invitee object, whether or not Shared Activities is enabled Entities Not Supported in After Undelete Triggers Certain objects can't be restored, and therefore, shouldn't have after undelete triggers. CollaborationGroup CollaborationGroupMember FeedItem FeedComment Considerations for Update Triggers Field history tracking honors the permissions of the current user. If the current user doesn't have permission to directly edit an object or field, but the user activates a trigger that changes an object or field with history tracking enabled, no history of the change is recorded. Considerations for the Salesforce Side Panel for Saleforce for Outlook When an email is associated to a record using the Salesforce Side Panel for Salesforce for Outlook, the email associations are represented in the WhoId or WhatId fields on a task record. Associations are completed after the task is created, so the Task.WhoId and Task.WhatId fields aren't immediately available in before or after Task triggers for insert and update events, and their values are initially null. The WhoId and WhatId fields are set on the saved task record in a subsequent operation, however, so their values can be retrieved later. See Also: Triggers for Chatter Objects

Triggers and Order of Execution

When you save a record with an insert, update, or upsert statement, Salesforce performs the following events in order. Note Before Salesforce executes these events on the server, the browser runs JavaScript validation if the record contains any dependent picklist fields. The validation limits each dependent picklist field to its available values. No other validation occurs on the client side. On the server, Salesforce: Loads the original record from the database or initializes the record for an upsert statement. Loads the new record field values from the request and overwrites the old values. If the request came from a standard UI edit page, Salesforce runs system validation to check the record for: Compliance with layout-specific rules Required values at the layout level and field-definition level Valid field formats Maximum field length When the request comes from other sources, such as an Apex application or a SOAP API call, Salesforce validates only the foreign keys. Prior to executing a trigger, Salesforce verifies that any custom foreign keys do not refer to the object itself. Salesforce runs user-defined validation rules if multiline items were created, such as quote line items and opportunity line items. Executes all before triggers. Runs most system validation steps again, such as verifying that all required fields have a non-null value, and runs any user-defined validation rules. The only system validation that Salesforce doesn't run a second time (when the request comes from a standard UI edit page) is the enforcement of layout-specific rules. Executes duplicate rules. If the duplicate rule identifies the record as a duplicate and uses the block action, the record is not saved and no further steps, such as after triggers and workflow rules, are taken. Saves the record to the database, but doesn't commit yet. Executes all after triggers. Executes assignment rules. Executes auto-response rules. Executes workflow rules. If there are workflow field updates, updates the record again. If the record was updated with workflow field updates, fires before update triggers and after update triggers one more time (and only one more time), in addition to standard validations. Custom validation rules, duplicate rules, and escalation rules are not run again. Executes processes. If there are workflow flow triggers, executes the flows. The pilot program for flow trigger workflow actions is closed. If you've already enabled the pilot in your org, you can continue to create and edit flow trigger workflow actions. If you didn't enable the pilot in your org, use the Flows action in Process Builder instead. Executes escalation rules. Executes entitlement rules. If the record contains a roll-up summary field or is part of a cross-object workflow, performs calculations and updates the roll-up summary field in the parent record. Parent record goes through save procedure. If the parent record is updated, and a grandparent record contains a roll-up summary field or is part of a cross-object workflow, performs calculations and updates the roll-up summary field in the grandparent record. Grandparent record goes through save procedure. Executes Criteria Based Sharing evaluation. Commits all DML operations to the database. Executes post-commit logic, such as sending email. Note During a recursive save, Salesforce skips steps 8 (assignment rules) through 17 (roll-up summary field in the grandparent record). Additional Considerations Please note the following when working with triggers. The order of execution isn't guaranteed when having multiple triggers for the same object due to the same event. For example, if you have two before insert triggers for Case, and a new Case record is inserted that fires the two triggers, the order in which these triggers fire isn't guaranteed. When a DML call is made with partial success allowed, more than one attempt can be made to save the successful records if the initial attempt results in errors for some records. For example, an error can occur for a record when a user-validation rule fails. Triggers are fired during the first attempt and are fired again during subsequent attempts. Because these trigger invocations are part of the same transaction, static class variables that are accessed by the trigger aren't reset. DML calls allow partial success when you set the allOrNone parameter of a Database DML method to false or when you call the SOAP API with default settings. For more details, see Bulk DML Exception Handling. If your org uses Contacts to Multiple Accounts, anytime you insert a non-private contact, an AccountContactRelation is created and its validation rules, database insertion, and triggers are executed immediately after the contact is saved to the database (step 6). When you change a contact's primary account, an AccountContactRelation may be created or edited, and the AccountContactRelation validation rules, database changes, and triggers are executed immediately after the contact is saved to the database (step 6). If you are using before triggers to set Stage and Forecast Category for an opportunity record, the behavior is as follows: If you set Stage and Forecast Category, the opportunity record contains those exact values. If you set Stage but not Forecast Category, the Forecast Category value on the opportunity record defaults to the one associated with trigger Stage. If you reset Stage to a value specified in an API call or incoming from the user interface, the Forecast Category value should also come from the API call or user interface. If no value for Forecast Category is specified and the incoming Stage is different than the trigger Stage, the Forecast Category defaults to the one associated with trigger Stage. If the trigger Stage and incoming Stage are the same, the Forecast Category is not defaulted. If you are cloning an opportunity with products, the following events occur in order: The parent opportunity is saved according to the list of events shown above. The opportunity products are saved according to the list of events shown above. Note If errors occur on an opportunity product, you must return to the opportunity and fix the errors before cloning. If any opportunity products contain unique custom fields, you must null them out before cloning the opportunity. Trigger.old contains a version of the objects before the specific update that fired the trigger. However, there is an exception. When a record is updated and subsequently triggers a workflow rule field update, Trigger.old in the last update trigger won't contain the version of the object immediately prior to the workflow update, but the object before the initial update was made. For example, suppose an existing record has a number field with an initial value of 1. A user updates this field to 10, and a workflow rule field update fires and increments it to 11. In the update trigger that fires after the workflow field update, the field value of the object obtained from Trigger.old is the original value of 1, rather than 10, as would typically be the case.

Calling a Class Method from a Trigger

You can call public utility methods from a trigger. Calling methods of other classes enables code reuse, reduces the size of your triggers, and improves maintenance of your Apex code. It also allows you to use object-oriented programming.

Triggers for Chatter Objects

You can write triggers for the FeedItem and FeedComment objects. Trigger Considerations for FeedItem, FeedAttachment, and FeedComment Only FeedItems of type TextPost, LinkPost, HasLink, ContentPost, and HasContent can be inserted, and therefore invoke the before or after insert trigger. User status updates don't cause the FeedItem triggers to fire. While FeedPost objects were supported for API versions 18.0, 19.0, and 20.0, don't use any insert or delete triggers saved against versions before 21.0. For FeedItem, the following fields are not available in the before insert trigger: ContentSize ContentType In addition, the ContentData field is not available in any delete trigger. Triggers on FeedItem objects run before their attachment and capabilities information is saved, which means that ConnectApi.FeedItem.attachment information and ConnectApi.FeedElement.capabilities information may not be available in the trigger. The attachment and capabilities information may not be available from these methods: ConnectApi.ChatterFeeds.getFeedItem, ConnectApi.ChatterFeeds.getFeedElement, ConnectApi.ChatterFeeds.getFeedPoll, ConnectApi.ChatterFeeds.getFeedElementPoll, ConnectApi.ChatterFeeds.postFeedItem, ConnectApi.ChatterFeeds.postFeedElement, ConnectApi.ChatterFeeds.shareFeedItem, ConnectApi.ChatterFeeds.shareFeedElement, ConnectApi.ChatterFeeds.voteOnFeedPoll, and ConnectApi.ChatterFeeds.voteOnFeedElementPoll FeedAttachment is not a triggerable object. You can access feed attachments in FeedItem update triggers through a SOQL query. For example: 01 trigger FeedItemTrigger on FeedItem (after update) { 02 03 List<FeedAttachment> attachments = [SELECT Id, Title, Type, FeedEntityId 04 FROM FeedAttachment 05 WHERE FeedEntityId IN :Trigger.new ]; 06 07 for (FeedAttachment attachment : attachments) { 08 System.debug(attachment.Type); 09 } 10 } When a feed item with associated attachments is inserted, the FeedItem is inserted first, then the FeedAttachment records are created next. On update of a feed item with associated attachments, the FeedAttachment records are inserted first, then the FeedItem is updated. As a result of this sequence of operations, FeedAttachments are available in update triggers only, and aren't available in insert triggers. The following feed attachment operations cause the FeedItem update triggers to fire. A FeedAttachment is added to a FeedItem and causes the FeedItem type to change. A FeedAttachment is removed from a FeedItem and causes the FeedItem type to change. FeedItem triggers aren't fired when inserting or updating a FeedAttachment that doesn't cause a change on the associated FeedItem. You can't insert, update, or delete FeedAttachments in before update and after update FeedItem triggers. For FeedComment before insert and after insert triggers, the fields of a ContentVersion associated with the FeedComment (obtained through FeedComment.RelatedRecordId) are not available. Other Chatter Trigger Considerations Apex code uses extra security when executing in a Chatter context. To post to a private group, the user running the code must be a member of that group. If the running user isn't a member, you can set the CreatedById field to be a member of the group in the FeedItem record. When CollaborationGroupMember is updated, CollaborationGroup is automatically updated as well to ensure that the member count is correct. As a result, when CollaborationGroupMember update or delete triggers run, CollaborationGroup update triggers run as well. See Also: Entity and Field Considerations in Triggers Object Reference for Salesforce and Force.com: FeedItem Object Reference for Salesforce and Force.com: FeedAttachment Object Reference for Salesforce and Force.com: FeedComment Object Reference for Salesforce and Force.com: CollaborationGroup Object Reference for Salesforce and Force.com: CollaborationGroupMember

Using Trigger Exceptions

You sometimes need to add restrictions on certain database operations, such as preventing records from being saved when certain conditions are met. To prevent saving records in a trigger, call the addError() method on the sObject in question. The addError() method throws a fatal error inside a trigger. The error message is displayed in the user interface and is logged. The following trigger prevents the deletion of an account if it has related opportunities. By default, deleting an account causes a cascade delete of all its related records. This trigger prevents the cascade delete of opportunities. Try this trigger for yourself! If you've executed the previous example, your org has an account called Apples & Oranges with a related opportunity. This example uses that sample account. Using the Developer Console, add the following trigger. trigger AccountDeletion on Account (before delete) { // Prevent the deletion of accounts if they have related opportunities. for (Account a : [SELECT Id FROM Account WHERE Id IN (SELECT AccountId FROM Opportunity) AND Id IN :Trigger.old]) { Trigger.oldMap.get(a.Id).addError( 'Cannot delete account with related opportunities.'); } } In the Salesforce user interface, navigate to the Apples & Oranges account's page and click Delete. In the confirmation popup, click OK. Find the validation error with the custom error message Cannot delete account with related opportunities. Disable the AccountDeletion trigger. If you leave this trigger active, you can't check your challenges. From Setup, search for Apex Triggers. On the Apex Triggers page, click Edit next to the AccountDeletion trigger. Deselect Is Active. Click Save.

Sample Callout

public class CalloutClass { @future(callout=true) public static void makeCallout() { HttpRequest request = new HttpRequest(); // Set the endpoint URL. String endpoint = 'http://yourHost/yourService'; request.setEndPoint(endpoint); // Set the HTTP verb to GET. request.setMethod('GET'); // Send the HTTP request and get the response. HttpResponse response = new HTTP().send(request); } } This example shows the trigger that calls the method in the class to make a callout asynchronously. 1 trigger CalloutTrigger on Account (before insert, before update) { CalloutClass.makeCallout(); } This section offers only an overview of callouts and is not intended to cover callouts in detail. For more information, see Invoking Callouts Using Apex in the Apex Developer Guide.

Example of a trigger using context variables that return Boolean value to indicate whether the trigger was fired due to an update or some other event. Useful when a trigger combines multiple events.

trigger ContextExampleTrigger on Account (before insert, after insert, after delete) { if (Trigger.isInsert) { if (Trigger.isBefore) { // Process before insert } else if (Trigger.isAfter) { // Process after insert } } else if (Trigger.isDelete) { // Process after delete } }


Related study sets

CH 55 Clients with disorder of the male reproductive system

View Set

Chapter 7: Creativity & Innovation

View Set