Monday, 30 September 2013

Trigger

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){

                                 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){

                                 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&nbsp;:Trigger.newMap.keySet() and StageName='Closed - Lost'];
   
    List<Opportunity> opptysClosedWon = [select id, name, closedate, stagename
            from Opportunity where 
            accountId IN&nbsp;:Trigger.newMap.keySet() and StageName='Closed - Won'];
   
    for(Account a&nbsp;: 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&nbsp;:Trigger.newMap.keySet()
         and  (StageName='Closed - Lost' or StageName = 'Closed - Won'))
         from Account where Id IN&nbsp;:Trigger.newMap.keySet()];
   
    //Loop through Accounts only once
    for(Account a&nbsp;: 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 =&nbsp;: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&nbsp;: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...
                    }
               }  


No comments:

Post a Comment