function readOnly(count){ }
Starting November 20, the site will be set to read-only. On December 4, 2023,
forum discussions will move to the Trailblazer Community.
+ Start a Discussion
sfdcIssfdcIs 

try catch block question

I have a before insert and update trigger that updates the account.ownerid based on criteria that is met by matching the account.billing state to zone.state__c custom object. I created a try catch block to catch and exception and throw the error to a custom errorlog object.  But it doesn't  seem to catch it. Is this because a query exception is not considered a dml exception? alsoi have A method called getNumDMl(), does this method count all exceptions or just dml ones? I want to basically catch as many exceptions as possible and throw to error log. any advice would be great

 

catch (QueryException e)
{
Error_Log__c eLog = new Error_Log__c();
List<Error_Log__c> eLogList = new List<Error_Log__c>();
for (Integer iex = 0; iex < e.getNumDml(); iex++)
{
eLog.Description__c = e.getDmlMessage(iex);
eLogList.add(eLog);
}
insert eLogList;
}

Best Answer chosen by Admin (Salesforce Developers) 
crop1645crop1645

sfdcls:

 

Glad we were able to help

 

1. In 'before' triggers, the try-catch block is 'generally' there to catch exceptions in your apex code logic (null pointers, array index, etc.). As you now know, it won't catch errors that occur during validation rules or downstream triggers that fire as a result of workflow field updates

 

2. I would recommend putting more data into the error message including exception type and line number so when that call to IT comes, you have something to go by without requiring the user to repeat the test with the debug log turned on.  I would also review your validation rules to make sure they are clear and 'actionable' to the end user as those are the ones they are most likely to see.

 

3. You simply can't count failed rows in the exception catch block because the 'before' trigger try-catch isn't catching DML exceptions on your SObject X. For a typical end user scenario, they are only updating one row - the record they are 'saving'. 

 

Bulk trigger idiom is there to handle data loader, API, web service, or other operations (such as VF controllers or 'after' triggers on different Sobjects doing updates/inserts on 1 or more of your Sobject X) - And in any of these cases, it is the caller that invokes the SObject  X trigger that needs to wrap that update/insert DML operation with try-catch - and then, if the exception is DmlException, you have the # of rows.

 

When the Sobject X trigger is invoked via a SAVE on the end user's Force.com UI, it is SFDC itself that has the try-catch surrounding the 'Save' - and its action is to rollback all updates and display an error message to the end user

All Answers

sfdcfoxsfdcfox

A queryexception is reserved for when you have a malformed query (such as "SELECT Foo FORM Bar" ( should be "FROM" ) using a dynamic query problem with query-variable mismatches (e.g. using a non-list variable and trying to assign muliple rows). You'll be wanting a DmlException instead. getNumDML() wil return just the failed rows, not all rows. If you want to catch ANY exception, catch Exception (the parent of all exceptions).

sfdcIssfdcIs

ok looks like I need to use the general exception called 'Exception'.

 

For this, could I use getNumDML() as the method to count the # exceptions? I couldn't find a general exception counter for this. Any idea what i could use the counter in my for statement  to catch everything.

 

catch (Exception e)
{
Error_Log__c eLog = new Error_Log__c();
List<Error_Log__c> eLogList = new List<Error_Log__c>();
for (Integer iex = 0; iex < e.getNumDml(); iex++)
{
eLog.Description__c = e.getDmlMessage(iex);
eLogList.add(eLog);
}
insert eLogList;
}

crop1645crop1645

An alternative that I regularly use is rather than do DML with insert or update statements, I use the Database.insert or Database.update methods.

 

1> They allow for partial successes

2> You get back a list of Database.saveResult objects that in turn tell you if row[i] was a success, and if not, what the error was on that row[i]

 

Much more fine-grained control over what actually happened in a bulk DML operation

 

sfdcIssfdcIs

I see the benefit for database methods and with partial processing.  What I'm struggling with  is that this is a before insert /update trigger so I'm trying catch the Exceptions. The insert dml on the error log is processing records that were already caught as an exception so how would I incorpoate this database method for this scenari?. Especially a batch insert, I want to be able to catch exceptions for each row.

 

Database.SaveResult SR = database.insert(eLogList);
for(Database.Error err: SR.getErrors())
{

// throw error to the error log here?
}

crop1645crop1645

sfdcls:

 

A few principles at work here for before update/insert triggers

 

1. If the trigger is on some Sobject X, then you update the fields on X simply by changing values in the Trigger.new list that is part of the Trigger context. No DML statements needed.

 

2. If the trigger is on some SObject X and you want to update a different SObject Y - this is best done in After update/insert triggers and you can use either:

* Database.insert or Database.update methods

* insert or update statements

 

3. If you are using Database.xxx methods, you wouldn't normally use try-catch around them to catch DML errors (rather, the try-catch would be to catch null pointer exceptions) as the Database.xxx methods give you programmatic control over each row's insert/update status

 

4. If you want to detect error conditions on SObject X in the before/after trigger for X, then use if statements and the SObject method addError -- this will tell SFDC that the relevant row in the Trigger.new list is in error and should not be committed. The addError method has the same logical effect as a validation rule - just that it occurs before validation rules because before triggers execute ahead of validation rules.

 

sfdcIssfdcIs

Thank you so much this. This is very helpful. My situation would apply to #1 so trigger.new passes it to the method in my class. Although #4 makes sense, instead of throwing an error to the user with .addError method, the requirement is to exit out of the trigger and insert the exception into the error_log__c.  What is the best way for me to loop through any type of exception that is caught? I origially had this code in an after insert/update and worked well using getNumDml to catch dmlException but i want use a general exception handler (Exception)  to catch all exceptions. this is where i am stuck.  Maybe I need to remove the try/catch block all together and use an if statement to catch the error?

 

My 2nd issue is trying to re-create an error so i can pass coverage.

 

public with sharing class AccountUtilities
{
public static void ZoneAssign(List<Account> aList)
{
try
{

List<Zone__c> tList = [select id, zip_start__c, zip_end__c, state__c,User__c from Zone__c];
for(Account a: aList)
{
for(integer i= 0; i< tList.size(); i++)
{

if(a.BillingPostalCode>= tList.get(i).zip_start__c && a.BillingPostalCode<=tList.get(i).zip_end__c )
{

a.Zone_c = tList.get(i).id;
a.OwnerId = tList.get(i).User__c;
}
}
}
}
}
catch (dmlException e)
{

// just temporary until i figure this out
System.debug('There was an error!');
}
/* Error_Log__c eLog = new Error_Log__c();
List<Error_Log__c> eLogList = new List<Error_Log__c>();
for (Integer iex = 0; iex < getNumDml ; iex++)
{
eLog.Description__c = e.getDmlMessage(iex);
eLogList.add(eLog);
}
insert eLogList;
} */
}
}

cropzimcropzim

Let's deal with these in turn

 

#1 - I'm a bit puzzled -- you want the user to be able to insert/update a record and if there are errors, not be told that there are errors? Assuming I have this wrong...

 

* A Trigger is going to implicitly update/insert the changed fields in Trigger.new unless you have called addError on that SObj instance.

* Even after the trigger executes, validation rules will fire on X and they could cause a rollback. The trigger can't catch this case as the trigger has lost context

* A VF controller can override the Save method and you can do the insert/update explicitly with a Database.insert or Database.update and detect all errors, including validation rule errors.  You can then save those errors to your error log SObject and tell the user that the update/insert failed using ApexPages.addMessage().  Of course, the VF controller only operates from the UI - either on a single SObject or a set of SObjects (such as a mass edit from a list). It will not be invoked for data loader or API calls.

 

#2 - To force exceptions 

 

Here is what I do --

 

1. I have a static class with some public booleans like forceNullPointer = false;

2. In my production code, I test for the value of MyStatic.Class.forceNullPointer. If true, then I execute code that causes a null pointer exception

3. In my testmethod, I set the value of MyStaticClass.forceNullPointer = true in order to trip the exception

 

For testing DML errors, a similar technique can be used - for example, by making a required field null before the DML operation

 

sfdcIssfdcIs

thank you for reply. What I meant for #1 is that if there is an exception from this trigger, instead of the user being notified the exception is inserted as a record into the error_log__c object. Then a standard workflow notification will be sent to the IT team to  review the exception in the error_log. From what I read, the .addError method stops the transaction so if I display a message to the end user such as "You have recieved an error, and IT team will get back to you" would I still be able to catch the exception and insert it into the error log? Looks like I can do this with a VF controller but we're not doing any UI modifications.  I also had this same try catch block in an after insert trigger which explcitly had a dml statement and it caught the dmlException but i can't seem to catch the exceptions on a before insert/update. also, i will check the static class on null-pointer to force an exception. If you have example of the static class that would be great.

sfdcfoxsfdcfox

If you call so much as a single addError, your Error_Log__c record will be toast, so you can't both display a friendly error to the user AND log the event (at least, not including the debug logs).

 

Edit: I mean, you can use addError, but it has to be caught before it reaches the system level (e.g. is caught by a try-catch block somewhere). You still can't directly report the error to the user, though. You could have a custom field, and use a VF page that displays the error message, if any. I've seen the likes for places where it would be advisable to have a warning instead of an actual error (e.g. a transaction failed).

crop1645crop1645

sfdcfox (who is very wise) is correct

 

I go back to the previous suggestion -- if your application requirement can be met by a VF controller, then you can control all the DML by overriding the SAVE action; you can also log. Validation rule errors can be caught. 

sfdcIssfdcIs

yeah just trying figure our how to catch any type of exceptions throw into the log. is anythuing wrong with my catch block?? im trying reporudce an error and nothing works

 

catch (Exception e)
{
System.debug('There was an error!');
}
/* Error_Log__c eLog = new Error_Log__c();
List<Error_Log__c> eLogList = new List<Error_Log__c>();
for (Integer iex = 0; iex < 2; iex++)
{
eLog.Description__c = e.getDmlMessage(iex);
eLogList.add(eLog);
}
insert eLogList;
} */
}
}

crop1645crop1645

sfdcls:

 

Your catch block per se is fine catch(Exception e) {..} will catch any exception thrown within the associated try{..} block.

 

However, as stated earlier, it will not catch update/insert errors due to validation rules or downstream triggers/workflow field updates/more validation rules/triggers.  Those can not be caught in a before trigger as those DML events occur after the trigger script code has ended.

 

The code you have commented out has conceptual issues:

 

  • e.getDmlMessage(iex) can only be used if the Exception is DmlException - you are catching all exceptions such as StringExceptions, TypeExceptions, NullPointerExceptions, ...including DmlExceptions

 

sfdcIssfdcIs

crop1645, after fully digesting all the posts and reading up on order of execution in salesforce, i can see why I can't catch these errors AND why they can't be inserted into the error log.  However, I can't do a vf page right now since it's beyond scope.  So i am left with the conclusion that the only way to handle exception right now is to use the  .addError method and alert the user.  The reason why I am placing an exception handler is as you guys know, it's a suggested best practice so it's not like I have to insert into the error log as long i have a handler to catch the issue.   I am going to fix the logic using .addError and catch  all exceptions in 1 catch block and display  something like 'There has been an unexpected error, please contact IT 1800-111-1111';

2 questions:

1. As long as I handle these exceptions this way, am i still within best practices for before insert/update exception handling?  assuming the vf page can't be build to way later since they specifically don';t want any custom UI 

2.  If i catch a general exception   ---- //catch (Exception e) , I don;t see a method to count # of failed rows other than getNumDml(). is there one i can use for Exception since this is not a handler specifically to catch dmlException.

 

You guys have been awesome - great learning experience so far

 

fyi - i know i need to update my code below  and will mark this as a solutions so others can see it  and give credit to u guys but just showing the latest

 

catch (Exception e)
{
Error_Log__c eLog = new Error_Log__c();
List<Error_Log__c> eLogList = new List<Error_Log__c>();
for (Integer iex = 0; iex < e.getNumDml(); iex++)
{
eLog.Description__c = e.getDmlMessage(iex);
eLogList.add(eLog);
}
insert eLogList;
}
}
}

crop1645crop1645

sfdcls:

 

Glad we were able to help

 

1. In 'before' triggers, the try-catch block is 'generally' there to catch exceptions in your apex code logic (null pointers, array index, etc.). As you now know, it won't catch errors that occur during validation rules or downstream triggers that fire as a result of workflow field updates

 

2. I would recommend putting more data into the error message including exception type and line number so when that call to IT comes, you have something to go by without requiring the user to repeat the test with the debug log turned on.  I would also review your validation rules to make sure they are clear and 'actionable' to the end user as those are the ones they are most likely to see.

 

3. You simply can't count failed rows in the exception catch block because the 'before' trigger try-catch isn't catching DML exceptions on your SObject X. For a typical end user scenario, they are only updating one row - the record they are 'saving'. 

 

Bulk trigger idiom is there to handle data loader, API, web service, or other operations (such as VF controllers or 'after' triggers on different Sobjects doing updates/inserts on 1 or more of your Sobject X) - And in any of these cases, it is the caller that invokes the SObject  X trigger that needs to wrap that update/insert DML operation with try-catch - and then, if the exception is DmlException, you have the # of rows.

 

When the Sobject X trigger is invoked via a SAVE on the end user's Force.com UI, it is SFDC itself that has the try-catch surrounding the 'Save' - and its action is to rollback all updates and display an error message to the end user

This was selected as the best answer
sfdcIssfdcIs

crop1645 - thank a million for the last post.  I got my exception handler to work. I ended up creating multiple try catch blocks. 2 of which I know ill occur at the business logic level - queryException and nullPointerException.  I plan on building a VF page to catch errors caught on validation rules or when triggers fire again as result of workflow..for now, I placed a general exception block at the end to catch anything else at business logic level and the insertion into the error log worked for nullPointer.  Also notice instead of getNumDml, the loop ends since I referenced the List<Account> aList which are the records passed from trigger.new. any issues you see here for this one?  I'm probably going to get rid of the logic of throwing the error into the error_log and instead use the .addError method like the first 2 and suggest monitoring the users in the debug log.  i think this will work now...the @future async operation to create the error in the log is an interesting concept but looks like it's catch 22 based on the other thread. what do you think so far?i think this should work until the vf page is built later...what do u think? now i need to finish up the test class

 

catch (System.NullPointerException e)
{
for(Account a: aList)
{
a.addError('There was an error with the record since a field was not populated correctly, please contact IT at 1888-888-8888 for asssitance, error number ' + e.getLineNumber());
}
}
catch (System.queryException e)
{
for(Account a: aList)
{
a.addError('There was an error with the record since the system recognize zero or multiple Territory records, please contact IT at 1888-888-8888 for assistance, error number ' + e.getLineNumber());
}
}
catch(Exception e)
{
System.debug('There was an unexpected exception - see log details below');
Error_Log__c eLog = new Error_Log__c();
List<Error_Log__c> eLogList = new List<Error_Log__c>();
for (Integer iex = 0; iex < aList.size(); iex++)
{
eLog.Description__c = 'Error line number: ' + e.getLineNumber() + 'Error Message ' + e.getMessage();
eLogList.add(eLog);
}
insert eLogList;
}
}

crop1645crop1645

sfdcls:

 

Without knowing the code in the try block; it is quite likely that you will know the specific Account where the error occurred and you won't need to reject the whole batch - just the record (Account) in error. That is, call addError only on the SObject in error.  This way the Data Loader and other batch invokers of your trigger can handle partial batch successes (DataLoader implicitly will).

 

For this to work, the variable bound to the 'account in error' will need to be declared outside the try{} block so as to be available to the catch {} block.

 

Since you are obviously concerned about a robust reporting system, may I also suggest adopting an error code naming system for your addError statements. For example:

 

a.addError('[A-01] There was an error with the record since a field was not populated correctly, please contact IT at 1888-888-8888 for assistance' + e.getLineNumber()); and

 

a.addError(''[A-02] There was an error with the record since the system recognize zero or multiple Territory records, please contact IT at 1888-888-8888 for assistance' + e.getLineNumber());

 

This makes it easy to find the spot in the code where the error occurred.  I do this also with Validation Rules.

 

As for the @future, I'm convinced that they won't execute if SFDC is going to rollback the transaction - which SFDC will do on an addError.

 

 

 

sfdcIssfdcIs

Eric- you saved me from making a huge mistake. I forgot about the inability to handle partial successes during a batch load in your previous post. I wouldn;t have know that until i did a batch test in my test class.  basically, the code in my try block is  not going to work.  I'll declare a var above the try statement so it;s within the scope of the catch block. Thank you also for recommending the addError convention- that makes way more sense - I will update that as well. Here is my try block.  Im going to update the code now but let me know if you see any other issues. Also, i had to change the territory custom object to zone__c

 

public static void ZoneAssign(List<Account> aList)
{
try
{

List<Zone__c> zList = [select id,SalesRep__c, recordTypeId from Zone__c];
Map<Id,RecordType> rtMap = new Map<Id,RecordType>([Select id,Name from RecordType where sObjectType IN ('Account','Zone__c')]);

for(Account a: aList)
{
for(integer i= 0; i< tList.size(); i++)
{
if(rtMap.get(a.recordtypeid).Name == rtMap.get(tList.get(i).recordtypeid).Name)
{


a.Zone__c = zList.get(i).id;
a.OwnerId = zList.get(i).Zone__c;

}
}
}
}

sfdcIssfdcIs

Hey Eric- so I was going to use the database methods to handle partial processing but for before insert/update, dml and database methods won't work right?  I saw in your previous post that I could use if statements - is there an example some where on wiki I could digest?  thinking...ummmm...

 

also, I found this on the wiki but i think this will only work on an after insert/update

 

1
Database.SaveResult[] lsr = Database.update(accounts, false);
2

3
for(Database.SaveResult sr : lsr){
4
if (!sr.isSuccess()) {
5
myMethodToaddToErrorObjectList(sr);
6
}
7
}

 

 
crop1645crop1645

sfdcls:

 

Before triggers

* You can't/shouldn't use Database.xxx methods on the triggered SObject X.

* You can mark only those triggered SObjects in error with addError; any triggered SObjects without addError will commit - provided the trigger invoker is designed to handle partial updates - Data Loader, for example, is designed to handle partial updates - so is inline View edits.  Any other custom client that invokes your triggers has to be designed to handle partial updates -- if written in APEX, by using the Database.xxx methods

 

After triggers

* In order to do any DML, an after trigger must explicitly use a DML statement like 'update' or 'insert' -- or - use the Database.xxx methods.  In the latter case, you can do partial batch updates.

 

Your code fragment to detect and process Database.saveResult[] is correct

 

 

sfdcIssfdcIs

Thank you.  s

* You can't/shouldn't use Database.xxx methods on the triggered SObject X


I interpret statement that the triggered sObject in this case the Account, I can't/shouldn't use database methods because before triggers happen on the triggered object so due the order of execution, I don't/can't use dml/database methods execute the insert/update

 

Any other custom client that invokes your triggers has to be designed to handle partial updates -- if written in APEX, by using the Database.xxx methods

 


I interpret this statement that when the trigger is fired from a custom app, database methods need to be used but this is where i get confused. isn't that contradictory to the above? my apologies if i am not understanding that 

 

Your last statement validates that  the code stub to detect and process Database.saveResult[] is correct but how can I used the .saveResult[] method if I can't use the Database.insert statements in a before trigger? 

 

wanted to understand before i start using database methods to handle partial success in my before trigger

 

 

 

 

 

crop1645crop1645

sfdcls:

 

Let me try this another way:

 

Use Case 1 - User clicks New or Edit on an SFDC SObject X. No VF controller is used

 

1. The before Trigger is invoked

2. Trigger.new will contain a list of size 1 - the SObject x that was edited/created

3. Trigger.new will contain all the values from SObject x including the edited/typed-in values from the form

4. The Trigger code contains logic to do validations (that can't be done with SFDC Validation Rules) and derivations of other fields in x that you want the code to do, rather than make the user enter the values

5. The Trigger script ends..reaches the last statement. Assume no calls to addError were made

6. SFDC fires Validation Rules

7. Assume no Validation rules failed

8. Assume no workflows or after triggers

9. SFDC commits x

 

Use Case 2 - User does mass edit from inline View

 

1. The before Trigger is invoked

2. Trigger.new will contain a list of size n -all SObject x that were mass edited

3. Trigger.new will contain all the values from a list of SObject x including the new values

4. The Trigger code contains logic to do validations (that can't be done with SFDC Validation Rules) and derivations of other fields in list of x that you want the code to do, rather than make the user enter the values

5. The Trigger script ends..reaches the last statement. Assume no calls to addError were made

6. SFDC fires Validation Rules

7. Assume no Validation rules failed

8. Assume no workflows or after triggers

9. SFDC commits all list of x

 

Use Case 3 is Data Loader - it works just like Use Case 2

 

Use Case 4 is an after trigger on Y that updates 5 X's

 

0. After Trigger on Y uses Database.xxx methods on X

1. The before Trigger on X is invoked

2. Trigger.new will contain a list of size 5 -all SObject x that were part of step 0

3. Trigger.new will contain all the values from a list of SObject x including the new values

4. The Trigger code contains logic to do validations (that can't be done with SFDC Validation Rules) and derivations of other fields in list of x that you want the code to do, rather than make the user enter the values

5. The Trigger script ends..reaches the last statement. Assume (for example)  3 of the 5 x's had calls to addError

6. SFDC fires Validation Rules only on the two error-free x

7. Assume these Validation rules succeed

8. Assume no workflows or after triggers

9. SFDC commits 2 of the 5 x's

10. after trigger regains control and uses Database.saveResult[] to examine the success of step 0. Three of the SaveResult[] members will have isSuccess() = false

 

Based on these use cases, you should be able to see that the before trigger on X shouldn't do DML operations on X because SFDC does that implicitly when the trigger script hits the end (well, after a few more things have to pass - see SFDC Order of Execution).

 

But, code that deliberately makes a database operation on X can be written to do partial updates by using the Database.xxx methods and it is up to the code that makes those DML operations on X to decide what it should do if there are partial successes.  This is application specific. For something like Data Loader, this is an easy decision for the Data Loader designers to do -- if in a batch of 50, 3 fail, the other 47 will commit as Data Loader treats each row update/insert as independent.

 

That is the beauty of writing all SFDC triggers bulkified -- they enable the callers to do batch operations.  When the caller is a form, only one record is in the Trigger.new list -- but this is just the degenerate case.

sfdcIssfdcIs

This is probably one of the best threads I have read so far.  Thank you for the detail and illustrations for each use case.  Due to this explanation, I was able to perform a bulk update with the dataloader and resulted in partial successes.  Notice the for loop starts at the top of the try block that uses the same sObject var so that .addError can be isolated to a single record instead of rejecting the entire batch.  I plan on fine tuning the error handlers now to make the validation look cleaner. Let me know if you see any other issues but I think it looks good now - BIG THANKS ERIC.

 

So..you mention in a prior thread, dataloader will implicitly handle partial successes otherwise we would have explicitly reference it in our code.

 

Question I have is from a batch negative / partial test coverage perspective if I want to test negative and partial success in the same test method, we need to expliclty use database.insert instead of a DML operation since it would roll back the entire transaction which wouldn;t cover the partiai success test, correct? Last question is, I haven;t implemented batch apex before but if this was millions of records for Accounts, could I use the same utility class in conjunction with a batch apex class that handles the transaction async? How would that work?

 

        
            List<Zone__c> zList = [select id,  recordTypeId from Zone__c];
            Map<Id,RecordType> rtMap = new Map<Id,RecordType>([Select id,Name from RecordType where sObjectType IN

            ('Account','Zone__c')]);
    
            for(Account a:aList)
            {
               try
              {
                for(integer i= 0; i< zList.size(); i++)
                {  
         
                    if( rtMap.get(a.recordtypeid).Name ==rtMap.get(zList.get(i).recordtypeid).Name)
                    {   
                            a.Zone__c = zList.get(i).id;
                            a.OwnerId = zList.get(i).rep__c;
                    }            
                }
            }
    
            catch (System.NullPointerException e)
             {
                     a.addError('There was an  error with the record since a field was not populated correctly, please contact IT at 

                     1888-888-8888 for asssitance, error number ' + e.getLineNumber());

             }
            catch (System.queryException e)
             {
                     a.addError('There was an  error with the record since the system recognize zero or multiple Zone records, please

                     contact IT at 1888-888-8888 for assistance, error number ' + e.getLineNumber());
             }   

           }

         }   
}

crop1645crop1645

sfdcls:

 

Glad I was able to help.

 

1. How to test for partial success?

 

The technique I use is a bit hacky but you could use the Test.isRunningTest() method directly in your production code.Then, you could create a test method that set some static variable Z to true and then, in the production code if Test.isRunningTest() && Z, force a database error (query exception, null pointer exception, validation rule failure) on, say, every 10th record in the batch. Then your test method can detect whether the records 0-9, 11-19, etc passed and records 10,20, .. failed.

 

2. Batch APEX is just another example of a client that wants to update many X's - code it with Database.updates (inserts) upon X and allow for partial successes with the optAllOrNone argument.  Your trigger will execute just like it does in the DataLoader Use Case above - each record that has an error will issue an addError and the batch APEX code can decide what to do with partial successes -- such as send an email with a log of # successes/#failures

 

 

If you haven't already, please mark one of the above posts as a Solution

 

sfdcIssfdcIs

Thank you crop-

 

i will see if I can pass coverage today.  Couple question on batch apex

 

1. I read that for batch apex,  interface database.batchable needs to be written. I also understand how this is similar to the dataloader use case.  However, how does the trigger invoke this class.? I am not sure where the batch apex class falls between the trigger and apex class that has my business logic.   Do I simply just add this batch apex class.method name  in the trigger?

 

2. As programmers, we should always develop scalable code.  What should be  considered when apex needs to be written in batch apex? I ask because shouldn't we assume that the data will out-grow the standard governor limits(e.g. 10k transaction in single dml). If this is the case, shouldn't we always code using batch apex?  Or perhaps the question is what should be async vs sync.

 

3. Assuming that #1 was true(i need 2 classes, i trigger), what happens to the governor limits in the apex class that has my business logic.  

 

 

crop1645crop1645

sfdcls:

 

 

You'll need two classes (plus your usual trigger code):

 

1. A class that creates an instance of the batchable class 'MyBatchableClass' and then calls Database.executeBatch(..)  - this class gets invoked by some user action or external event.

2. A batchable class MyBatchableClass (read the APEX Developers Guide here) that implements four methods -- a constructor(..), start(..), execute(..), and finish(..).

 

The trick is:

 

1> you provide through the constructor the SOQL query to select your rows (all one million of them) 

 

2> On the start method (called by SFDC), you use the Database.getQueryLocator() on the SOQL string to get a cursor to the result set

 

3> On the execute() method (also called by SFDC) , you'll be passed from SFDC 200 (or some other batch size, configurable) set of rows of your SObject X. This execute method then does a Database.update on all these rows (the 200). This will call your trigger. You do any error processing on Database.SaveResult[]

 

SFDC repeatedly calls the execute() method until the full result set from the SOQL query is processed. This could take many minutes depending on size of the set

 

4> Finally, the finish() method is called by SFDC. This is where you write an email message or other log type action with the results of the batch

 

The discussion about whether you should always have batch APEX coded really depends on your use cases.  Do you need to mass update thousands of rows in a single user event or external system event?  This is a whole other topic and is well-addressed better than I ever could in this book by Dan Appleman 'Advanced Apex Programming for Salesforce.com and Force.com'.

 

I'll let others weigh in here but most triggers are unlikely to exceed governor limits unless the trigger's caller passes in a long list of objects or the work to process each object requires many script statements. Again, Mr. Appleman's book takes you through all this in detail

 

sfdcIssfdcIs

found another twister here......since this is a before trigger.  dmlExceptions can not be caught so this is a situation where user updates an existing record through the UI and transaction comes to a hault with .addError.  Therefore, if I don't handle the dml exception and Run Test, I will hit 100% but will cause a test method failue.  If I don't want  the test failure then I have to assert it  but would only be able to hit 86%. Which one is better??

 

catch (System.NullPointerException e)
{
  // process exception with .addError

}
catch (System.DmlException e)
{
  // process exception
}

 

Test Class

catch(System.NullPointerException e)
{
System.assertEquals('System.NullPointerException' , e.getTypeName());
}
catch(System.DmlException e)
{
System.assertEquals('System.DmlException', e.getTypeName() );
}
}

 

 

sfdcIssfdcIs

Ok... nevermind.  I just found out that you can't execute DML operations on RecordType objects.  Looks like the only way is to hard-code the RecordType.DeveloperName or RecordType.Name.  Also, is there a benefit to using developername versus name?

crop1645crop1645

I use DeveloperName only because it is less likely to be inadvertently changed in the Force.com UI

sfdcIssfdcIs

So I had to add a second method to my accou utility class and upon account insert or update, it updates the related contacts which is why I can use the before trigger and it works great but noticed at the end, i am processing the records with database.update and checking through the list for errors. However, if i want to display the error message on the account associated to these contacts, how would I throw a custom message on the screen without .addError?

 

 

public static void test(List<Account> aNewList, List<Account> aOldList )
{
for(Account a: aNewList)
{
//
for(integer i=0; i< aNewList.size(); i++)
{
// opt out check
{
acctsMap.put(a.Id, a);
}
}
//for loop key set
{

//assign to variables
}
/Database.update(updatedContacts);
Database.SaveResult[] lsr = Database.update(updatedContacts, false);
for(Database.SaveResult sr : lsr)
 if (!sr.isSuccess()) 
 {
 //  need a custom message but how do i display it here????
 }
}

crop1645crop1645

Assuming you are not using VF and are instead using the standard pages, then the best you can do is create a custom rich text field 'Account.Errors__c' and then populate this with your own error message, then update the Account (via update/Database.update if using an after trigger)

 

In this rich text field, you can use APEX to create HTML tags to make the message red/orange/green.

 

Thus, the transaction is committed but any 'informational messages' can be preserved on the record for inspection. Of course, you'll need to clear this Account.Errors__c field out on the next Account update ...or do some sort of running log

 

Forgive me if I've gotten a bit lost on this whole thread as it has been going on for a while and I've been diverted onto my own work <g>

sfdcIssfdcIs

crop1645 -  I appreciate your time. No problem, we all have our own jobs and I was fortunate enough to get some great advice.  The rich text field is very creative and I'm see if I can do that just for fun. I think I may just use a standard DML update on contacts and handle the exception with.adderror...thanks again

sfdcIssfdcIs

Hi crop i deployed my trigger but the method that processes the opt out is hitting a governor limit of  > 10000 contacts? How come this is happening when i don;t have 10k contacts per account

crop1645crop1645

sfdcls --

 

1. Given how much you have done since the beginning of this interaction, please repost your trigger and any supporting classes -- PLEASE use the code insert button in the post so it is easier to read.

 

2. Turn your production debug log on and repeat your transaction for the submitting user. then look at the log to see why you may be hitting a limit.  You may be fetching all Contacts rather than just 'contacts ...where accountID IN {..}'

 

 

Shubham Jadhav 70Shubham Jadhav 70
Refer https://salesforceidiot.blogspot.com/2023/05/error-logging-framework-in-salesforce.html