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 fow we cant peform all operations like DML operations. The following scenarios commonly implemented with triggers ! validation r"le is re#"ired that is too complex to de$ne on the database ob%ect "sing form"la expression. Two ob%ects m"st be kept synchroni&ed' when a record in one ob%ect is "pdated ' a trigger "pdates the corresponding record in the other. (ecords of an ob%ect m"st be arg"mented with val"es from another ob%ect' a complex calic"lation or external data via web service call. Syntax Trigger trigger)*ame on +b%ect)*ame,-efore or !fter 'DML operation./ 00 !pex code 1 Trigger can take two keywords i.e 1. Before or After 2. DML operation . Trigger is to be exec"ted before or after the data base operation is saved. Before : -efore keyword is "sed in trigger' "pdate or validate records before they are saved in database. After !fter keyword is "sed in trigger to access $eld val"es that are set by database and to a2ect changes other records. DML operation the DML operations of database are Insert 3. 4pdate 5. Delete 6. Merge 7. 4psert 8. 4ndelete E9 of before insert Tr igger trigger :heap;rice on -ook))c ,before insert./ for,-ook))c bTrigger.new./
if,b.price))c<7==./ b.price))c>b.price))c?,b.price))c @ =.5.A 1 1 1 Bor I*CE(T and 4;D!TE triggers the list of records in the transcation is assigned the variable Trigger.*ew. Bor 4;D!TE 'DELETE '4*DELETE triggers the read only original list of records assigned the variable Trigger.+ld. !fter exec"ting 4;D!TE trigger it can display on read?only records. If we can display the earror message in a page we can "se following syntax. +b%ect.addError,Dwrite the error messageE.A If we can display the earror message in the partic"lar $eld we can "sing following syntax. +b%ect.$eld*ame.addError,Dwrite the error messageE.A E9 of before 4pdate Trigger ,if we can "se before "pdate ' $rst ofall wecan open partic"lar record that record can be assigned to Trigger.*ew here simply we can modi$ed $elds and save it' if we click the save b"tton before Trigger is $red and "pdate the $elds with c"rrent val"es. trigger :heap;rice on -ook))c ,before "pdate./ listF-ook))c< li>new ListF-ook))c<,.A
for,-ook))c bTrigger.new./
if,b.price))c<7==./ b.price))c>b.price))c?,b.price))c @ =.5.A 1 1 1 00 (E!D !*D G(ITE B+(M!T !nd second scenario we can write before "pdate trigger here also we can open partic"lar record'in this case we can "se Trigger.old 'the opend record assigned to Trigger.old b"t it is already saved in data base it cant assigned to Trigger.*ew'thatsway we cant perform any operation in that partic"lar record.,it is readable format.. EX: trigger :heap;rice on -ook))c ,before "pdate./ listF-ook))c< li>new ListF-ook))c<,.A
for,-ook))c bTrigger.old./
if,b.price))c<7==./ b.price))c>b.price))c?,b.price))c @ =.5.A } } } 00 (E!D +*LH B+(M!T *+TE I. In the before "pdate trigger if we can "se Trigger.old 'then the record is opend only read mode. 3. and also after "pdate trigger if we can "se trigger.new ' then the record is opend only read mode. see the following table here I > contains = > not contains new old -efore Insert I = -efore 4pdate I I -efore Delete = I !fter Insert I = !fter 4pdate I I !fter Delete = I !fter 4nDelete = I If the Trigger is de$ned m"ltiple events like insert '"pdate ' deleteJ..etc. at that time we will "se following -oolean variables. i.e 1. Trigger.isBefore 2. Trigger.isAfter 3. Trigger.isInsert 4. Trigger.isp!ate ". Trigger.isDe#ete $. Trigger.isn!e#ete EX : trigger :heap;rice on -ook))c ,before insert'before delete'before "pdate./ if,Trigger.Is-efore./ if,Trigger.IsInsert./ listF-ook))c< li>new ListF-ook))c<,.A for,-ook))c bTrigger.new./ if,b.price))c<I==./ b.price))c.addError,Kyo" cant insertK.A 1 1 1 if,Trigger.isDelete./ for,-ook))c bTrigger.old./ if,b.price))c<3==./ b.addError,Kyo" cant delete this recordK.A 1 1 1 if,Trigger.is4pdate. / for,-ook))c bTrigger.old./ if,b.price))c<LM./ b.addError,Kyo" cant "pdate this recordK.A 1 1 1 1 1 -"lkifying !pex code refers to the concept of making s"re the code properlyhandles more than one record at a time. Ghen a batch of records initiates !pex' a single instance of that !pex code is exec"ted' b"t it needs to handle all of the records in that given batch. Bor example' a trigger co"ld be invoked by an Borce.com C+!; !;I call that inserted a batch of records. Co if a batch of records invokes the same !pex code' all of those records need to be processed as a b"lk' in order to write scalable code and avoid hitting governor limits. %ere is an exa&p#e of poor#y 'ritten (o!e t)at on#y )an!#es one re(or!: Trigger acco"ntTestTrggr on !cco"nt ,before insert' before "pdate. / 00This only handles the $rst record in the Trigger.new collection 00-"t if more than one !cco"nt initiated this trigger' those additional records 00will not be processed !cco"nt acct > Trigger.newN=OA ListF:ontact< contacts > Nselect id' sal"tation' $rstname' lastname' email from :ontact where acco"ntId >acct.IdOA 1 fo##o'ing (o!e is sa&p#e )o' to )an!#e a## in(o&ing re(or!s Trigger acco"ntTestTrggr on !cco"nt ,before insert' before "pdate. / ListFCtring< acco"nt*ames > new ListFCtring<,.A 00Loop thro"gh all records in the Trigger.new collection for,!cco"nt a Trigger.new./ 00:oncatenate the *ame and billingCtate into the Description $eld a.Description > a.*ame P KK P a.-illingCtate 1 1 A*oi! S+,L ,-eries insi!e .+/ Loops : The previo"s -est ;ractice talked abo"t the importance of handling all incoming records in a b"lk manner. The example was to "se a for loop to iterate over all of the records in the Trigger.new collection. ! common mistake is that #"eries are placed inside a for loop. There is a governor limit that enforces a maxim"m n"mber of C+QL #"eries. Ghen #"eries are placed inside a for loop' a #"ery is exec"ted on each iteration and the governor limit is easily reached. Instead' move the C+QL #"ery o"tside of the for loop and retrieve all the necessary data in a single #"ery. %ere is an exa&p#e of a )a*ing a 0-ery insi!e a for #oop: trigger acco"ntTestTrggr on !cco"nt ,before insert' before "pdate. / 00Bor loop to iterate thro"gh all the incoming !cco"nt records for,!cco"nt a Trigger.new. / 00TRIC B+LL+GI*S Q4E(H IC I*EBBI:IE*T !*D D+EC*KT C:!LE 00Cince the C+QL Q"ery for related :ontacts is within the B+( loop' if this trigger is initiated 00with more than I== records' the trigger will exceed the trigger governor limit 00of maxim"m I== C+QL Q"eries. ListF:ontact< contacts > Nselect id' sal"tation' $rstname' lastname' email from :ontact where acco"ntId >a.IdOA for,:ontact c contacts. / Cystem.deb"g,K:ontact IdNK P c.Id P KO' Birst*ameNK P c.$rstname P KO' Last*ameNK P c.lastname PKOK.A c.Description>c.sal"tation P K K P c.$rst*ame P K K P c.lastnameA 00TRIC B+LL+GI*S DML CT!TEME*T IC I*EBBI:IE*T !*D D+EC*KT C:!LE 00Cince the 4;D!TE dml operation is within the B+( loop' if this trigger is initiated 00with more than I7= records' the trigger will exceed the trigger governor limit 00of I7= DML +perations maxim"m. "pdate cA 1 1 1 Cince there is a C+QL #"ery within the for loop that iterates across all the !cco"nt ob%ects that initiated this trigger' a #"ery will be exec"ted for each !cco"nt. !n individ"al !pex re#"est gets a maxim"m of I== C+QL #"eries before exceeding that governor limit. Co if this trigger is invoked by a batch of more than I== !cco"nt records' the governor limit will throw a r"ntime exception. Teep in mind that the same is tr"e for DML operations as well. Meaning' avoid having DML operations ,insert' "pdate' delete. inside a for loop since that will also "nnecessarily exceed the governor limit pertaining to DML operations. In this example' beca"se there is a limit of I7= DML operations per re#"est' a governor limit will be exceeded after the I7=th contact is "pdated. Rere is the optimal way to Kb"lkifyK the code to eUciently #"ery the contacts in a single #"ery and only perform a single "pdate DML operation. EX of -sing DML operations o-t si!e of for #oop : Trigger acco"ntTestTrggr on !cco"nt ,before insert' before "pdate. / 00This #"eries all :ontacts related to the incoming !cco"nt records in a single C+QL #"ery. 00This is also an example of how to "se child relationships in C+QL ListF!cco"nt< acco"ntsGith:ontacts > Nselect id' name' ,select id' sal"tation' description' $rstname' lastname' email from :ontact. from !cco"nt where Id I* Trigger.newMap.keyCet,.OA ListF:ontact< contactsTo4pdate > new ListF:ontact</1A 00 Bor loop to iterate thro"gh all the #"eried !cco"nt records for,!cco"nt a acco"ntsGith:ontacts./ 00 4se the child relationships dot syntax to access the related :ontacts for,:ontact c a.:ontacts./ Cystem.deb"g,K:ontact IdNK P c.Id P KO' Birst*ameNK P c.$rstname P KO' Last*ameNK P c.lastname PKOK.A c.Description>c.sal"tation P K K P c.$rst*ame P K K P c.lastnameA contactsTo4pdate.add,c.A 1 1 00*ow o"tside the B+( Loop' perform a single 4pdate DML statement. "pdate contactsTo4pdateA 1 B-#1ify yo-r %e#per Met)o!s : Exec"ting Q"eries or DML operations with in iterations adds risks that the governer limits will be exceeded' this is also tr"e for any helper or "tility methods. governer limits are calic"lated at r"n time'after re#"est is initiated'any apex code exec"tes in that transcation and share the governer limits. if atrigger "ses some apex methods written in helper classes'its important that those apex methods are properly designed to handle b"lk records. these methods sho"ld be written set of records'especially if the methods has C+QL or DML operations. Bor example if the apex method perform C+QL #"ery'that method sho"ld receive collection of records'so when it perform the #"ery'it can perform the #"ery for all the records in the apex transcation' otherwise if the apex method is called individ"ally for each record being processed the apex transcation will r"n ineUciently r"n #"ries and possibly exceed the n"mber #"eries allowed in that transcation ' the same is tr"e for DML operations also. %ere is a sa&p#e t)at -ses (o##e(tions ine2(ient#y: EX : trigger acco"ntTrigger on !cco"nt ,before delete' before insert' before "pdate. / 00This code ineUciently #"eries the +pport"nity ob%ect in two seperate #"eries ListF+pport"nity< opptys:losedLost > Nselect id' name' closedate' stagename from +pport"nity where acco"ntId I*VnbspATrigger.newMap.keyCet,. and Ctage*ame>K:losed ? LostKOA ListF+pport"nity< opptys:losedGon > Nselect id' name' closedate' stagename from +pport"nity where acco"ntId I*VnbspATrigger.newMap.keyCet,. and Ctage*ame>K:losed ? GonKOA for,!cco"nt aVnbspA Trigger.new./ 00This code ineUciently has two inner B+( loops 00(ed"ndantly processes the List of +pport"nity Lost for,+pport"nity o opptys:losedLost./ if,o.acco"ntid >> a.id. Cystem.deb"g,KDo more logic here...K.A 1 00(ed"ndantly processes the List of +pport"nity Gon for,+pport"nity o opptys:losedGon./ if,o.acco"ntid >> a.id. Cystem.deb"g,KDo more logic here...K.A 1 1 1 the main iss"e of the previo"s snippet is "nnessary #"ering of the opert"nity records in two separate #"eries. over come this iss"e to write following code trigger acco"ntTrigger on !cco"nt ,before delete' before insert' before "pdate. / 00This code eUciently #"eries all related :losed Lost and 00:losed Gon opport"nities in a single #"ery. ListF!cco"nt< acco"ntGith+pptys > Nselect id' name' ,select id' name' closedate' stagename from +pport"nities where acco"ntId I*VnbspATrigger.newMap.keyCet,. and ,Ctage*ame>K:losed ? LostK or Ctage*ame > K:losed ? GonK.. from !cco"nt where Id I*VnbspATrigger.newMap.keyCet,.OA 00Loop thro"gh !cco"nts only once for,!cco"nt aVnbspA acco"ntGith+pptys./ 00Loop thro"gh related +pport"nities only once for,+pport"nity o a.+pport"nities./ if,o.Ctage*ame >> K:losed ? GonK./ Cystem.deb"g,K+pport"nity :losed Gon...do some more logic here...K.A 1else if,o.Ctage*ame >>K:losed ? LostK./ Cystem.deb"g,K+pport"nity :losed Lost...do some more logic here...K.A 1 1 1 1 ,-erying Large Data Sets : The total n"mber of records that can be exec"tes by C+QL is 7====' if ret"rning a b"lk of records ca"ses to exceed o"r heap si&e' then C+QL #"ery for loop "sed to instead. if the n"mber of records si&e exceeds heap si&e The following code arise r"ntime exception. !cco"ntNO>Nselect id from !cco"ntOA instead "se a C+QL for loop as in one of the following example E9 for,ListF!cco"nt< a Nselect id'name from !cco"nt where name LITE KashokKO./ "pdate aA 1 se 3f-t-re Appropriate#y !s artic"lated thro"gho"t this article' it is critical to write yo"r !pex code to eUciently handle b"lk or many records at a time. This is also tr"e for asynchrono"s !pex methods ,those annotated with the Wf"t"re keyword.. Even tho"gh !pex written within an asynchrono"s method gets its own independent set of higher governor limits' it still has governor limits. !dditionally' no more than ten Wf"t"re methods can be invoked within a single !pex transaction. Rere is a list of governor limits speci$c to the Wf"t"re annotation *o more than I= method calls per !pex invocation *o more than 3== method calls per Calesforce license per 36 ho"rs. The parameters speci$ed m"st be primitive dataypes' arrays of primitive datatypes' or collections of primitive datatypes. Methods with the f"t"re annotation cannot take s+b%ects or ob%ects as arg"ments. Methods with the f"t"re annotation cannot be "sed in Xis"alforce controllers in either getMethod*ame or setMethod*ame methods' nor in the constr"ctor. the !pex trigger ineUciently invokes an asynchrono"s method for each !cco"nt record it wants to process EX : trigger acco"nt!syncTrigger on !cco"nt ,after insert' after "pdate. / for,!cco"nt a Trigger.new./ 00 Invoke the Wf"t"re method for each !cco"nt 00 This is ineUcient and will easily exceed the governor limit of 00 at most I= Wf"t"re invocation per !pex transaction async!pex.process!cco"nt,a.id.A 1 1 Rere is the !pex class that de$nes the Wf"t"re method EX : global class async!pex / Wf"t"re p"blic static void process!cco"nt,Id acco"ntId. / ListF:ontact< contacts > Nselect id' sal"tation' $rstname' lastname' email from :ontact where acco"ntId >VnbspAacco"ntIdOA for,:ontact c contacts./ Cystem.deb"g,K:ontact IdNK P c.Id P KO' Birst*ameNK P c.$rstname P KO' Last*ameNK P c.lastname PKOK.A c.Description>c.sal"tation P K K P c.$rst*ame P K K P c.lastnameA 1 "pdate contactsA 1 1 Cince the Wf"t"re method is invoked within the for loop' it will be called *?times ,depending on the n"mber of acco"nts being processed.. Co if there are more than ten acco"nts' this code will throw an exception for exceeding a governor limit of only ten Wf"t"re invocations per !pex transaction. Instead' the Wf"t"re method sho"ld be invoked with a batch of records so that it is only invoked once for all records it needs to process EX: trigger acco"nt!syncTrigger on !cco"nt ,after insert' after "pdate. / 00-y passing the Wf"t"re method a set of Ids' it only needs to be 00invoked once to handle all of the data. async!pex.process!cco"nt,Trigger.newMap.keyCet,..A 1 !nd now the Wf"t"re method is designed to receive a set of records EX : global class async!pex / 3f-t-re p"blic static void process!cco"nt,CetFId< acco"ntIds. / ListF:ontact< contacts > Nselect id' sal"tation' $rstname' lastname' email from :ontact where acco"ntId I*VnbspAacco"ntIdsOA for,:ontact c contacts./ Cystem.deb"g,K:ontact IdNK P c.Id P KO' Birst*ameNK P c.$rstname P KO' Last*ameNK P c.lastname PKOK.A c.Description>c.sal"tation P K K P c.$rst*ame P K K P c.lastnameA 1 "pdate contactsA 1 1 4riting Test Met)o!s to 5erify Large Datasets : Rere is the poorly written contact trigger. Bor each contact' the trigger performs a C+QL #"ery to retrieve the related acco"nt. The invalid part of this trigger is that the C+QL #"ery is within the for loop and therefore will throw a governor limit exception if more than I== contacts are inserted0"pdated. EX : trigger contactTest on :ontact ,before insert' before "pdate. / for,:ontact ct Trigger.new./ !cco"nt acct > Nselect id' name from !cco"nt where Id>ct.!cco"ntIdOA if,acct.-illingCtate>>K:!K./ Cystem.deb"g,Kfo"nd a contact related to an acco"nt in california...K.A ct.email > Ktest)emailWtesting.comKA 00!pply more logic here.... 1 1 1 Rere is the test method that tests if this trigger properly handles vol"me datasets E9 p"blic class sampleTestMethod:ls / static testMethod void test!cco"ntTrigger,./ 00Birst' prepare 3== contacts for the test data !cco"nt acct > new !cco"nt,name>Ktest acco"ntK.A insert acctA :ontactNO contactsTo:reate > new :ontactNO/1A for,Integer x>=A xF3==AxPP./ :ontact ct > new :ontact,!cco"ntId>acct.Id'lastname>KtestK.A contactsTo:reate.add,ct.A 1 00*ow insert data ca"sing an contact trigger to $re. Test.startTest,.A insert contactsTo:reateA Test.stopTest,.A 1 1 *ote the "se of Test.startTest and Test.stopTest. Ghen exec"ting 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 set"p to do so witho"t a2ecting the governor limits available to the act"al code being tested. *ow letKs correct the trigger to properly handle b"lk operations. The key to $xing this trigger is to get the C+QL #"ery o"tside the for loop and only do one C+QL Q"ery E9 trigger contactTest on :ontact ,before insert' before "pdate. / CetFId< acco"ntIds > new CetFId<,.A for,:ontact ct Trigger.new. acco"ntIds.add,ct.!cco"ntId.A 00Do C+QL Q"ery MapFId' !cco"nt< acco"nts > new MapFId' !cco"nt<, Nselect id' name' billingCtate from !cco"nt where id in acco"ntIdsO.A for,:ontact ct Trigger.new./ if,acco"nts.get,ct.!cco"ntId..-illingCtate>>K:!K./ Cystem.deb"g,Kfo"nd a contact related to an acco"nt in california...K.A ct.email > Ktest)emailWtesting.comKA 00!pply more logic here.... 1 1 1 *ote how the C+QL #"ery retrieving the acco"nts is now done once only. If yo" re?r"n the test method shown above' it will now exec"te s"ccessf"lly with no errors and I==Y code coverage. A*oi!ing %ar!(o!ing : Rere is a sample that hardcodes the record type IDs that are "sed in an conditional statement. This will work $ne in the speci$c environment in which the code was developed' b"t if this code were to be installed in a separate org ,ie. as part of an !ppExchange package.' there is no g"arantee that the record type identi$ers will be the same. EX : for,!cco"nt a Trigger.new./ 00Error ? hardcoded the record type id if,a.(ecordTypeId>>K=I37=======ZG!rK./ 00do some logic here..... 1else if,a.(ecordTypeId>>K=I35=======Z7TmK./ 00do some logic here for a di2erent record type... 1 1 1 *ow' to properly handle the dynamic nat"re of the record type IDs' the following example #"eries for the record types in the code' stores the dataset in a map collection for easy retrieval' and "ltimately avoids any hardcoding. EX : 00Q"ery for the !cco"nt record types ListF(ecordType< rtypes > NCelect *ame' Id Brom (ecordType where s+b%ectType>K!cco"ntK and is!ctive>tr"eOA 00:reate a map between the (ecord Type *ame and Id for easy retrieval MapFCtring'Ctring< acco"nt(ecordTypes > new MapFCtring'Ctring</1A for,(ecordType rt rtypes. acco"nt(ecordTypes.p"t,rt.*ame'rt.Id.A for,!cco"nt a Trigger.new./ 004se the Map collection to dynamically retrieve the (ecord Type Id 00!void hardcoding Ids in the !pex code if,a.(ecordTypeId>>acco"nt(ecordTypes.get,KRealthcareK../ 00do some logic here..... 1else if,a.(ecordTypeId>>acco"nt(ecordTypes.get,KRigh TechK../ 00do some logic here for a di2erent record type... 1 1