Trigger :
Extension of
workflow is called as Trigger.
Triggers
are apex
code working in
concert with the
force.com database engine. It is
invoked when perform any
operation on database records.
Note : In
work flow we cant peform all operations
like DML operations.
The following
scenarios commonly implemented
with triggers :
A validation rule is required that is too complex to define on
the database object
using formula expression.
Two objects must be kept
synchronized, when a record in
one object is
updated , a trigger updates
the corresponding record
in the other.
Records of an object must be
argumented with values
from another object, a complex caliculation
or external data
via web service call.
Syntax :
Trigger
trigger_Name on Object_Name(Before or After ,DML operation){
// Apex code
}
Trigger can take
two keywords
i.e
1.
Before or After
2.
DML operation .
Trigger is
to be executed
before or after
the data base operation
is saved.
Before :
Before keyword
is used in
trigger, update or
validate records before they
are saved in
database.
After :
After keyword
is used in
trigger to access
field values that
are set by
database and to
affect changes other
records.
DML operation :
the DML operations of
database are
•
Insert 2. Update 3. Delete 4. Merge 5. Upsert 6. Undelete
EX of before
insert Tr igger :
trigger CheapPrice on Book__c (before insert){
for(Book__c b:Trigger.new){
if(b.price__c>500){
b.price__c=b.price__c-(b.price__c * 0.3);
}
}
}
•
For
INSERT and UPDATE
triggers the list
of records in
the transcation is assigned the
variable Trigger.New.
•
For UPDATE
,DELETE ,UNDELETE triggers the
read only original list of records
assigned the variable Trigger.Old.
•
After
executing UPDATE trigger it can
display on read-only records.
•
If we
can display the earror message in a page we can
use following syntax.
Object.addError(‘write the error message’);
•
If we can display
the earror message
in the particular field
we can using following
syntax.
Object.fieldName.addError(‘write the error
message’);
EX of
before Update Trigger :
(if we can
use before update , first ofall
wecan open particular record that record
can be assigned to Trigger.New here
simply we can modified fields and save
it, if we click the save button before Trigger is fired and update the fields
with current values)
trigger CheapPrice on Book__c (before update){
list<Book__c> li=new List<Book__c>();
for(Book__c b:Trigger.new){
if(b.price__c>500){
for(Book__c b:Trigger.new){
if(b.price__c>500){
b.price__c=b.price__c-(b.price__c * 0.3);
}
}
}
// READ AND WRITE FORMAT
And second scenario we can write before update trigger here also we
can open particular record,in this case we can use Trigger.old ,the opend
record assigned to Trigger.old but it is already saved in data base it cant
assigned to Trigger.New,thatsway we cant perform any operation in that
particular record.(it is readable format).
EX:
trigger CheapPrice on Book__c (before update){
list<Book__c> li=new List<Book__c>();
for(Book__c b:Trigger.old){
if(b.price__c>500){
for(Book__c b:Trigger.old){
if(b.price__c>500){
b.price__c=b.price__c-(b.price__c * 0.3);
}
}
}
// READ ONLY FORMAT
NOTE : 1. In the
before update trigger if we can use
Trigger.old ,then the
record is opend only read mode.
2. and also after
update trigger if
we can use
trigger.new , then the
record is opend
only read mode.
see the following table :
here 1 = contains 0 = not contains
new old
Before Insert 1 0
Before Update 1 1
Before Delete 0 1
After Insert 1 0
After Update 1 1
After Delete 0 1
After UnDelete 0 1
If
the Trigger is
defined multiple events
like insert ,update , delete…..etc.
at that
time we will use
following Boolean variables.
i.e 1.
Trigger.isBefore
2. Trigger.isAfter
3.
Trigger.isInsert
4.
Trigger.isUpdate
5.
Trigger.isDelete
6.
Trigger.isUndelete
EX :
trigger CheapPrice
on Book__c (before insert,before delete,before update){
if(Trigger.IsBefore){
if(Trigger.IsInsert){
list<Book__c> li=new
List<Book__c>();
for(Book__c b:Trigger.new){
if(b.price__c>100){
b.price__c.addError('you cant insert');
}
}
}
if(Trigger.isDelete){
for(Book__c b:Trigger.old){
if(b.price__c>200){
b.addError('you
cant delete this record');
}
}
}
if(Trigger.isUpdate) {
for(Book__c b:Trigger.old){
if(b.price__c>78){
b.addError('you cant update this record');
}
}
}
}
}
Bulkifying Apex code refers to the concept
of making sure the code properlyhandles more than one record at a time. When a
batch of records initiates Apex, a single instance of that Apex code is
executed, but it needs to handle all of the records in that given batch. For
example, a trigger could be invoked by an Force.com SOAP API call that inserted
a batch of records. So if a batch of records invokes the same Apex code, all of
those records need to be processed as a bulk, in order to write scalable code
and avoid hitting governor limits.
Here is an example
of poorly written code that only handles one record:
Trigger
accountTestTrggr on Account (before insert, before update) {
//This only handles the first record in the
Trigger.new collection
//But if more than one Account initiated
this trigger, those additional records
//will not be processed
Account acct = Trigger.new[0];
List<Contact> contacts = [select id,
salutation, firstname, lastname, email
from Contact where accountId
=:acct.Id];
}
following code is
sample how to handle all incoming records
Trigger
accountTestTrggr on Account (before insert, before update) {
List<String> accountNames = new
List<String>{};
//Loop through all records in the
Trigger.new collection
for(Account a: Trigger.new){
//Concatenate the Name and billingState
into the Description field
a.Description = a.Name + ':' +
a.BillingState
}
}
Avoid
SOQL Queries inside FOR Loops :
The previous Best
Practice talked about the importance of handling all incoming records in a bulk
manner. The example was to use a for loop to iterate over all of the records in
the Trigger.new collection. A common mistake is that queries are placed inside
a for loop. There is a governor limit that enforces a maximum number of SOQL
queries. When queries are placed inside a for loop, a query is executed on each
iteration and the governor limit is easily reached. Instead, move the SOQL
query outside of the for loop and retrieve all the necessary data in a single
query.
Here is an
example of a having a query inside a for loop:
trigger
accountTestTrggr on Account (before insert, before update) {
//For loop to iterate through all the
incoming Account records
for(Account a: Trigger.new) {
//THIS FOLLOWING QUERY IS INEFFICIENT AND
DOESN'T SCALE
//Since the SOQL Query for related
Contacts is within the FOR loop, if this trigger is initiated
//with more than 100 records, the trigger
will exceed the trigger governor limit
//of maximum 100 SOQL Queries.
List<Contact> contacts = [select
id, salutation, firstname, lastname, email
from Contact where
accountId =:a.Id];
for(Contact c: contacts) {
System.debug('Contact Id[' + c.Id +
'], FirstName[' + c.firstname + '],
LastName[' + c.lastname +']');
c.Description=c.salutation + ' ' +
c.firstName + ' ' + c.lastname;
//THIS FOLLOWING DML STATEMENT IS
INEFFICIENT AND DOESN'T SCALE
//Since the UPDATE dml operation is
within the FOR loop, if this trigger is initiated
//with more than 150 records, the
trigger will exceed the trigger governor limit
//of 150 DML Operations maximum.
update c;
}
}
}
Since there is a SOQL
query within the for loop that iterates across all the Account objects that
initiated this trigger, a query will be executed for each Account. An
individual Apex request gets a maximum of 100 SOQL queries before exceeding
that governor limit. So if this trigger is invoked by a batch of more than 100
Account records, the governor limit will throw a runtime exception.
Keep in mind that the same is
true for DML operations as well. Meaning, avoid having DML operations (insert,
update, delete) inside a for loop since that will also unnecessarily exceed the
governor limit pertaining to DML operations. In this example, because there is
a limit of 150 DML operations per request, a governor limit will be exceeded
after the 150th contact is updated.
Here is the optimal way to
'bulkify' the code to efficiently query the contacts in a single query and only
perform a single update DML operation.
EX of using
DML operations out side of for loop :
Trigger
accountTestTrggr on Account (before insert, before update) {
//This queries all Contacts related to the
incoming Account records in a single SOQL query.
//This is also an example of how to use child
relationships in SOQL
List<Account> accountsWithContacts =
[select id, name, (select id, salutation, description, firstname, lastname, email from
Contact) from Account where Id IN
:Trigger.newMap.keySet()];
List<Contact> contactsToUpdate = new
List<Contact>{};
// For loop to iterate through all the
queried Account records
for(Account a: accountsWithContacts){
// Use the child relationships dot syntax
to access the related Contacts
for(Contact c: a.Contacts){
System.debug('Contact Id[' + c.Id + '],
FirstName[' + c.firstname + '], LastName[' + c.lastname +']');
c.Description=c.salutation + ' ' +
c.firstName + ' ' + c.lastname;
contactsToUpdate.add(c);
}
}
//Now outside the FOR Loop, perform a single
Update DML statement.
update contactsToUpdate;
}
Bulkify your Helper Methods :
Executing Queries or DML
operations with in iterations adds risks
that the governer limits will be exceeded, this is also true for any
helper or utility methods. governer limits are caliculated at run time,after
request is initiated,any apex code executes in that transcation and share the
governer limits. if atrigger uses some apex methods written in helper
classes,its important that those apex methods are properly designed to handle
bulk records. these methods should be
written set of records,especially if the methods has SOQL or DML operations.
For example if the
apex method perform SOQL query,that method should receive collection of
records,so when it perform the query,it can perform the query for all the
records in the apex transcation, otherwise if the apex method is called
individually for each record being processed the apex transcation will run
inefficiently run quries and possibly exceed the number queries allowed in that
transcation , the same is true for DML
operations also.
Here is a
sample that uses collections inefficiently:
EX :
trigger accountTrigger on Account (before
delete, before insert, before update) {
//This code inefficiently queries the
Opportunity object in two seperate queries
List<Opportunity> opptysClosedLost =
[select id, name, closedate, stagename
from Opportunity where
accountId
IN :Trigger.newMap.keySet() and StageName='Closed - Lost'];
List<Opportunity> opptysClosedWon =
[select id, name, closedate, stagename
from Opportunity where
accountId
IN :Trigger.newMap.keySet() and StageName='Closed - Won'];
for(Account a : Trigger.new){
//This
code inefficiently has two inner FOR loops
//Redundantly
processes the List of Opportunity Lost
for(Opportunity o: opptysClosedLost){
if(o.accountid
== a.id)
System.debug('Do more logic here...');
}
//Redundantly processes the List of
Opportunity Won
for(Opportunity o: opptysClosedWon){
if(o.accountid
== a.id)
System.debug('Do more logic here...');
}
}
}
the main issue of the previous
snippet is unnessary quering of the
opertunity records in two separate queries.
over come this issue to write following code
trigger
accountTrigger on Account (before delete, before insert, before update) {
//This code efficiently queries all
related Closed Lost and
//Closed Won opportunities in a single
query.
List<Account> accountWithOpptys =
[select id, name, (select id, name, closedate,
stagename from Opportunities where accountId
IN :Trigger.newMap.keySet()
and
(StageName='Closed - Lost' or StageName = 'Closed - Won'))
from Account where Id
IN :Trigger.newMap.keySet()];
//Loop through Accounts only once
for(Account a :
accountWithOpptys){
//Loop through related Opportunities only once
for(Opportunity o: a.Opportunities){
if(o.StageName
== 'Closed - Won'){
System.debug('Opportunity
Closed Won...do some more logic here...');
}else
if(o.StageName =='Closed - Lost'){
System.debug('Opportunity
Closed Lost...do some more logic here...');
}
}
}
}
Querying
Large Data Sets :
The total number of records that
can be executes by SOQL is 50000, if returning a bulk of records causes to
exceed our heap size, then SOQL query for loop used to instead.
if the number of records size exceeds heap
size The following code arise runtime exception.
Account[]=[select
id from Account];
instead use a SOQL
for loop as in one of the following
example
EX :
for(List<Account> a: [select id,name
from Account where name LIKE 'ashok']){
update a;
}
Use @future Appropriately
As articulated throughout this article, it
is critical to write your Apex code to efficiently handle bulk or many records
at a time. This is also true for asynchronous Apex methods (those annotated
with the @future keyword).
Even though Apex written within an
asynchronous method gets its own independent set of higher governor limits, it
still has governor limits. Additionally, no more than ten @future methods can
be invoked within a single Apex transaction.
· Here is a list of
governor limits specific to the @future annotation:
· No more than 10
method calls per Apex invocation
· No more than 200
method calls per Salesforce license per 24 hours.
· The parameters specified
must be primitive dataypes, arrays of primitive
datatypes, or collections of primitive datatypes.
· Methods with the
future annotation cannot take sObjects or objects as arguments.
· Methods with the
future annotation cannot be used in Visualforce controllers in either
getMethodName or setMethodName methods, nor in the constructor.
the Apex trigger inefficiently invokes an
asynchronous method for each Account record it wants to process:
EX :
trigger
accountAsyncTrigger on Account (after insert, after update) {
for(Account a: Trigger.new){
// Invoke the @future method for each
Account
// This is inefficient and will easily
exceed the governor limit of
// at most 10 @future invocation per Apex
transaction
asyncApex.processAccount(a.id);
}
}
Here is the Apex
class that defines the @future method:
EX :
global class
asyncApex {
@future
public static void processAccount(Id
accountId) {
List<Contact> contacts = [select
id, salutation, firstname, lastname, email
from Contact where accountId
= :accountId];
for(Contact c: contacts){
System.debug('Contact Id[' + c.Id + '],
FirstName[' + c.firstname + '], LastName[' + c.lastname +']');
c.Description=c.salutation + ' ' +
c.firstName + ' ' + c.lastname;
}
update contacts;
}
}
Since the @future
method is invoked within the for loop, it will be called N-times (depending on
the number of accounts being processed). So if there are more than ten
accounts, this code will throw an exception for exceeding a governor limit of
only ten @future invocations per Apex transaction.
Instead, the
@future method should be invoked with a batch of records so that it is only invoked
once for all records it needs to process:
EX:
trigger
accountAsyncTrigger on Account (after insert, after update) {
//By passing the @future method a set of
Ids, it only needs to be
//invoked once to handle all of the data.
asyncApex.processAccount(Trigger.newMap.keySet());
}
And now the @future
method is designed to receive a set of records:
EX :
global class
asyncApex {
@future
public static void
processAccount(Set<Id> accountIds) {
List<Contact> contacts = [select
id, salutation, firstname, lastname, email from Contact where accountId
IN :accountIds];
for(Contact c: contacts){
System.debug('Contact Id[' + c.Id + '],
FirstName[' + c.firstname + '], LastName[' + c.lastname +']');
c.Description=c.salutation + ' '
+ c.firstName + ' ' + c.lastname;
}
update contacts;
}
}
Writing Test
Methods to Verify Large Datasets :
Here is the poorly written contact
trigger. For each contact, the trigger performs a SOQL query to retrieve the
related account. The invalid part of this trigger is that the SOQL query is
within the for loop and therefore will throw a governor limit exception if more
than 100 contacts are inserted/updated.
EX :
trigger contactTest
on Contact (before insert, before update) {
for(Contact ct: Trigger.new){
Account acct = [select id, name from Account where Id=:ct.AccountId];
if(acct.BillingState=='CA'){
System.debug('found a contact related
to an account in california...');
ct.email = 'test_email@testing.com';
//Apply more logic here....
}
}
}
Here is the test
method that tests if this trigger properly handles volume datasets:
EX :
public class
sampleTestMethodCls {
static testMethod void testAccountTrigger(){
//First, prepare 200 contacts for the test data
Account acct = new Account(name='test account');
insert acct;
Contact[] contactsToCreate = new Contact[]{};
for(Integer x=0; x<200;x++){
Contact ct = new
Contact(AccountId=acct.Id,lastname='test');
contactsToCreate.add(ct);
}
//Now insert data causing an contact trigger to fire.
Test.startTest();
insert contactsToCreate;
Test.stopTest();
}
}
Note the use of
Test.startTest and Test.stopTest. When executing tests, code called before
Test.startTest and after Test.stopTest receive a separate set of governor
limits than the code called between Test.startTest and Test.stopTest. This
allows for any data that needs to be setup to do so without affecting the
governor limits available to the actual code being tested.
Now let's correct
the trigger to properly handle bulk operations. The key to fixing this trigger
is to get the SOQL query outside the for loop and only do one SOQL Query:
EX :
trigger contactTest
on Contact (before insert, before update) {
Set<Id> accountIds = new Set<Id>();
for(Contact ct: Trigger.new)
accountIds.add(ct.AccountId);
//Do SOQL Query
Map<Id, Account> accounts = new Map<Id, Account>(
[select id, name, billingState from Account where id in :accountIds]);
for(Contact ct: Trigger.new){
if(accounts.get(ct.AccountId).BillingState=='CA'){
System.debug('found a contact
related to an account in california...');
ct.email = 'test_email@testing.com';
//Apply more logic here....
}
}
}
Note how the SOQL
query retrieving the accounts is now done once only. If you re-run the test
method shown above, it will now execute successfully with no errors and 100%
code coverage.
Avoiding Hardcoding
:
Here is a sample that
hardcodes the record type IDs that are used in an conditional statement. This
will work fine in the specific environment in which the code was developed, but
if this code were to be installed in a separate org (ie. as part of an
AppExchange package), there is no guarantee that the record type identifiers
will be the same.
EX :
for(Account a:
Trigger.new){
//Error - hardcoded the record type id
if(a.RecordTypeId=='012500000009WAr'){
//do some logic here.....
}else if(a.RecordTypeId=='0123000000095Km'){
//do some logic here for a different record type...
}
}
}
Now, to properly handle the dynamic
nature of the record type IDs, the following example queries for the record
types in the code, stores the dataset in a map collection for easy retrieval,
and ultimately avoids any hardcoding.
EX :
//Query for the
Account record types
List<RecordType> rtypes = [Select Name, Id From RecordType
where sObjectType='Account'
and isActive=true];
//Create a map between the Record Type Name and Id for easy retrieval
Map<String,String> accountRecordTypes = new Map<String,String>{};
for(RecordType rt: rtypes)
accountRecordTypes.put(rt.Name,rt.Id);
for(Account a: Trigger.new){
//Use the Map collection to
dynamically retrieve the Record Type Id
//Avoid hardcoding Ids in the Apex
code
if(a.RecordTypeId==accountRecordTypes.get('Healthcare')){
//do some logic here.....
}else
if(a.RecordTypeId==accountRecordTypes.get('High Tech')){
//do some logic here for a
different record type...
}
}