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
tdevmantdevman 

addError method

I have an Account before trigger and if a bulk account update happens, I don't want to exit out the entire batch but check each account record but i can't really use database method or could i???

here is a sample code stub and check out my add error method.  aList collection are records passed in from Trigger.new

 

       Map<id, Contact> cMap= new Map<id, Contact>();

        for (Contact c: cList){
            cMap.put(c.AccountId, c);
        }

        for (Account a: aList){
            if (cMap.containsKey(a.id)){
                 if( cMap.get(a.id).custom_field__c== null 
                    && cMap.get(a.id).custom_field__c==null ){
                    a.addError('missing contact info!);
                    }
            

Best Answer chosen by Admin (Salesforce Developers) 
sfdcfoxsfdcfox
http://www.salesforce.com/us/developer/docs/apexcode/Content/apex_dml_bulk_exceptions.htm

If allOrNone is true, addError reverts entire DML operation. If an uncaught exception reaches the DML layer, the entire operation is reverted.

If allOrNone is false, then addError has a different effect. First, all records are attempted. If any return with an error on an object or field, those records are "set aside", the governor limits are reset, and a second pass occurs. If there are any further errors, a third pass is attempted, again with governor limits reset. At this point, if it fails, the operation fails and is rolled back. Otherwise, all records that passed the final pass without causing an error are all committed, while those that were "set aside" are flagged as errors and will be reported.

AddError will also work on any field or record bound to a Visualforce page; you can show an error message without any DML operation at all.

All Answers

Yoganand GadekarYoganand Gadekar

I think you can use this method nly in following contexts:

Trigger.new in before insert and before updatetriggers, and on Trigger.old in before delete triggers

Avidev9Avidev9
  • addError method prevents any database operation to happen and rollbacks the DML action(due to which trigger spawned), and throws a error msg to the UI. 
  • If you have any other DML statement in your trigger like say "insert Contact" the trigger successfully process that.
  • I dont think you can stop a single record from insertion using addError.
  • You cannot use Database method in trigger context to prevent or block records DML
sfdcfoxsfdcfox
http://www.salesforce.com/us/developer/docs/apexcode/Content/apex_dml_bulk_exceptions.htm

If allOrNone is true, addError reverts entire DML operation. If an uncaught exception reaches the DML layer, the entire operation is reverted.

If allOrNone is false, then addError has a different effect. First, all records are attempted. If any return with an error on an object or field, those records are "set aside", the governor limits are reset, and a second pass occurs. If there are any further errors, a third pass is attempted, again with governor limits reset. At this point, if it fails, the operation fails and is rolled back. Otherwise, all records that passed the final pass without causing an error are all committed, while those that were "set aside" are flagged as errors and will be reported.

AddError will also work on any field or record bound to a Visualforce page; you can show an error message without any DML operation at all.
This was selected as the best answer
tdevmantdevman

sfdcfox- 

 

thank you for the post but the problem is- this is a before update trigger. And from my understanding we don't need to explicilty write a dml (e.g. update account) or use a database.updatemethod.  would I be able to use the database.update method in a before trigger?? My other question is, this is a trigger that throws an add error so there is no DML happening here. so how would i do a partial processing using the database.update and allOrNone parameter? here is what i was thikning:

 

List<Account> acctsToUpdate = new List<Account>();

Map<id, Contact> cMap= new Map<id, Contact>();

        for (Contact c: cList){
            cMap.put(c.AccountId, c);
        }

        for (Account a: aList){
            if (cMap.containsKey(a.id)){
                 if( cMap.get(a.id).custom_field__c== null 
                    && cMap.get(a.id).custom_field__c==null ){
                    a.addError('missing contact info!);
                    }

                  else{

                  acctsToUpdate.add(a);

                   }

                }

            }

         if(acctsToUpdate.size() >0){

            database.update(acctsToUpdate,false);

        }

                  
            

sfdcfoxsfdcfox

A trigger runs inside of a DML event; you don't need to use on the records in the trigger, nor can you specify allOrNone, because the DML event is already underway. I think this is the piece that you're missing. Partial record processing isn't handled by the trigger, it's handled by the system. Consider the following code:

 

public class controller {
    account[] defaultaccounts() {
        return new account[] {
            new account(name='Blocked'), new account(name='Not blocked') };
    }

    void dosave(boolean allornone) {
        account[] records = defaultaccounts();
        try {
            outputresults(database.insert(records,allornone));
        } catch(dmlexception e) {
            apexpages.addmessages(e);
        }
    }

    public void partialsave() {
        dosave(false);
    }
    
    public void allornone() {
        dosave(true);
    }
    
    public void outputresults(database.saveresult[] results) {
        for(integer i = 0; i < results.size(); i++) {
            apexpages.message newmessage;
            if(results[i].isSuccess()) {
                newmessage = new apexpages.message(apexpages.severity.info,'Record '+(i+1)+' saved okay!');
            } else {
                newmessage = new apexpages.message(apexpages.severity.info,'Record '+(i+1)+' failed to save:'+results[i].geterrors()[0].getmessage());
            }
            apexpages.addmessage(newmessage);
        }
    }
}

 

<apex:page controller="controller">
    <apex:form>
        <apex:pageMessages/>
        <apex:commandButton value="Partial Save" action="{!partialsave}"/>
        <apex:commandButton value="All or none Save" action="{!allornone}"/>
    </apex:form>
</apex:page>

 

trigger blockAccounts on Account (before insert) {
    for(Account record:trigger.new) {
        if(record.name=='Blocked') {
            record.name.addError('This name is not allowed');
        }
    }
}

If you use the partial save button, one account is created, and the other is not; the trigger doesn't need to concern itself with partial saves explicitly, as the DML subsystem automatically handles the retry cases. The all or none save will fail, and no records will be inserted.

 

 

tdevmantdevman

sfdcfox - i saw this in a similar thread but if i want to validate child objects so that if they don't meet a condition throw an error , otherwise perform a partial success using database method on a before trigger ,would this work or would processing come to a halt and exit the batch in this case

 

List<parent> plist = new List<parent>();

 

for(childObject child: childList){
   if(child.my_custom__c == null ){
     parentMap.get(child.ParentId).addError('not complete');

    else{

     parentList.add(parentMap.get(child.ParentId));

    }
  }

}

 

 

 

 

 

 

 

sfdcfoxsfdcfox
You simply need to add the error message to the parent record; there is no need to call an update on the parents if they're already in the trigger, because they're already being updated.
tdevmantdevman

But what about this comment "If allOrNone is false, then addError has a different effect" - I thikn this effect happens when it is not a before trigger. Is tat correct? My goal is to check each parent record instead of existing the entire batch.

sfdcfoxsfdcfox
This effect occurs outside of your trigger; your trigger doesn't need to know about how it was called, all it needs to do is perform its task. Don't try to make the work any more complex than it has to be.
tdevmantdevman

Reading again....since the before trigger is executed from a dml event, the record is already being updated since i'm within the context of a trigger so it's redundant to write another dml statement explicitly.  So if i have a test method THEN i can explicilty write the logic to partially process those records in the event an addError is thrown. - i think i got this down now

 

Also to assert the message i think i need to recover them from a database method...looks like it should be database.error.getMessage();  correct?

2 more question is

1. if the records are triggered from a apex dataloader update- that is partial processing out of the box right?

2. If you are using database methods to examine each record, is there any reason to use try catch with a database method? seems like the catch block would never hit

 

 

 for example: 

 

badChilds is a list of records that meet the crtieria to throw the adderror

 

Test.startTest();

 try{               
         database.update(badChilds.ParentSobject, false);
            }
         catch(Exception e){
         Boolean expectedError=  e.getMessage().contains('missing child info!') ? true : false;
          System.AssertEquals(expectedError, true); 
Test.stopTest(); 
sfdcfoxsfdcfox
You'll get your errors in the Database.SaveResult[] if allOrNone is false, or you'll have to try-catch the DmlException if allOrNone is true. The Apex Data Loader uses allOrNone set to false, so individual failures won't stop an entire batch from succeeding. You are correct in stating that a try-catch block when allOrNone is set to false will never catch an exception, because no exception will be thrown. If allOrNone is set to true, however, then you must use a try-catch block or your code will fail from an uncaught exception. Use a try-catch block only if you're writing code that uses allOrNone set to true (or the keyword modes, which also use allOrNone as true, e.g. "update badChilds;").
tdevmantdevman

sfdcfox i got everything to work but having an issue getting coverage with a future method. how come my assert fails? is there a better way to test the future class..not sure the best way to reproduce an exception

 

//main class called from parent object trigger
try{
for(parentObject po: poList){
if(childObject.childfield__c == null ){
parentObjectMap.get(childObject.parentObjectId).addError(errMsg);
poList.add(oMap.get(childObject.parentObjectId));
}
}
System.debug('Currently, '+ myScriptLimit +' have been executed');
}
catch(Exception ex) {
futureElog.createElogRec(ex.getMessage());
}
}
}

//future class
global class futureElog {
@future
public static void createElogRec(string eMessage){
My_Error_Log__c eLogRec = new My_Error_Log__c(
Error_Description__c= eMessage);
insert eMessage;
}
}

 

 

 

Test.startTest();
integer beforeCount = [SELECT COUNT()
FROM My_Error_Log__c];
parentObject.id = '000000000000000000';
update opp;

Test.stopTest();

integer afterCount = [SELECT COUNT() FROM My_Error_Log__c]; 

 

System.AssertEquals(beforeCount +1, afterCount);

tdevmantdevman

Ok..i was able to get the 100% coverage using isRunningtest() within the trigger and calling this before my catch in the main class. there was no other way to produce the exception from a trigger context

 

throw new TestException('err');

sfdcfoxsfdcfox
It's often hard to test exception handling when the exception depends on a situation outside of your control. Your workaround is probably the most reasonable one I could offer.