2

I have written a few triggers for our business org and have multiple validation rules in place.

I was wondering if there's a way to only show Custom Validation Rule's error message instead of a long list of "nonsense" for our users (like shown below):

"Apex trigger primaryLiaisonCount2 caused an unexpected exception, contact your administrator: primaryLiaisonCount2: execution of AfterUpdate caused by: System.DmlException: Update failed. First exception on row 0 with id 00100000006j9JYAAY; first error: FIELD_CUSTOM_VALIDATION_EXCEPTION, When the Account's Back-up field is set to BUCA, you must make a selection for Back-Up Coverage. If you're editing an Opportunity, please first go to the parent account and select a value for Back-Up Coverage.: [Back_Up_Type__c]: Trigger.primaryLiaisonCount2: line 34, column 1"

What they needed to see is only: "When the Account's Back-up field is set to BUCA, you must make a selection for Back-Up Coverage. If you're editing an Opportunity, please first go to the parent account and select a value for Back-Up Coverage."

This will help our users to be "less" confused. Any help is greatly appreciated!!

Updated Trigger below

trigger primaryLiaisonCount2 on Contact(after update) {
if(checkRecursive.runOnce()){
    List<Account> accList = new List<account>();
    List<Account> accListToUpdate = new List<account>();
    Set<Account> accSet = new Set<Account>();
    set<id> allAccIds = new Set<id>();    

for (Contact c1: trigger.new){
    //excluding Marketo, Ken Reyes, and Arnold one line below.
    if (c1.AccountID != null && c1.LastModifiedById != '00500000006wnoE' && c1.LastModifiedById != '00500000006xdMm' && c1.LastModifiedById != '00500000006wsIm'){
    allAccIds.add(c1.AccountID);
    } 
}


decimal amount1=[SELECT count() FROM Contact WHERE AccountID IN :allAccIds AND Primary_Liaison__c != null AND Marketing_Status__c != 'Gone From Company'];
decimal amount2=[SELECT count() FROM Contact WHERE AccountID IN :allAccIds AND Receive_Client_Survey__c = true AND Marketing_Status__c != 'Gone From Company'];
LIST<Account> acc1=[SELECT ID FROM Account WHERE ID IN :allAccIds AND recordtypeID = '012000000000j8S'];
list<Account> acc3=[SELECT ID From Account WHERE ID NOT IN :allAccIds AND recordtypeID ='012000000000j8S'];

 for (Contact c1: trigger.new){
    for (Account acc2:acc1){
     acc2.Primary_Liaisons__c = amount1;
     acc2.Receive_Client_Surveys__c = amount2;
     accList.add(acc2);           
     }
 }

   accSet.addall(accList);
   accListToUpdate.addall(accSet);
   Update accListToUpdate;
 }
}
3
  • What's the actual line the error is on? Likely you need to use addError instead of forcing update records; to error the record out through code instead.
    – dphil
    Dec 9 '14 at 19:48
  • Hey dphil! Here it is for "line 34" "Update accListToUpdate;" Can you tell me in detail since this trigger is more of a field update automation/workflow instead of a validation rule? Thanks again!! Dec 9 '14 at 19:58
  • 2
    Keep mind mind, it's never a good idea to hard code IDs.
    – dphil
    Dec 9 '14 at 20:37
3

Put your DML statement inside the "Try" section of a Try Catch block and then handle the DML error like this:

List<Custom__c> trgCusts; // this is the list you are trying to perform DML on

try {
    update trgCusts;
}
catch (DMLException e) {
    for (Custom2__c cust2 : trigger.new) {
        cust2.addError(e.getDmlMessage(0));
    }
}
catch (Exception e) {
    for (Custom2__c cust2 : trigger.new) {
        cust2.addError(e.getMessage());
    }
}

Be aware that this will fail every record in the trigger batch with the first record's error message - possibly desirable but if not you can use the Database.update() method to receive detailed information about which records fail and then map these back to the trigger records and only fail those that you want to.

Update

trigger primaryLiaisonCount2 on Contact(after update) {
    if(checkRecursive.runOnce()){
        List<Account> accList = new List<account>();
        List<Account> accListToUpdate = new List<account>();
        Set<Account> accSet = new Set<Account>();
        set<id> allAccIds = new Set<id>();    

        for (Contact c1: trigger.new){
            //excluding Marketo, Ken Reyes, and Arnold one line below.
            if (c1.AccountID != null && c1.LastModifiedById != '00500000006wnoE' && c1.LastModifiedById != '00500000006xdMm' && c1.LastModifiedById != '00500000006wsIm'){
            allAccIds.add(c1.AccountID);
            } 
        }


        decimal amount1=[SELECT count() FROM Contact WHERE AccountID IN :allAccIds AND Primary_Liaison__c != null AND Marketing_Status__c != 'Gone From Company'];
        decimal amount2=[SELECT count() FROM Contact WHERE AccountID IN :allAccIds AND Receive_Client_Survey__c = true AND Marketing_Status__c != 'Gone From Company'];
        LIST<Account> acc1=[SELECT ID FROM Account WHERE ID IN :allAccIds AND recordtypeID = '012000000000j8S'];
        list<Account> acc3=[SELECT ID From Account WHERE ID NOT IN :allAccIds AND recordtypeID ='012000000000j8S'];

        for (Contact c1: trigger.new){
            for (Account acc2:acc1){
                acc2.Primary_Liaisons__c = amount1;
                acc2.Receive_Client_Surveys__c = amount2;
                accList.add(acc2);           
            }
        }

        accSet.addall(accList);
        accListToUpdate.addall(accSet);

        try {
            update accListToUpdate;
        }
        catch (DMLException e) {
            for (Contact con : trigger.new) {
                con.addError(e.getDmlMessage(0));
            }
        }
        catch (Exception e) {
            for (Contact con : trigger.new) {
                con.addError(e.getMessage());
            }
        }
    }

}

The code inside each Catch block will only run if that type of Exception is thrown - so only one Catch block will ever run. That's why the fact they are both called "e" doesn't matter.

When performing DML if an exception occurs (e.g. validation rule) then a DMLException will be thrown which has a special method on it called getDmlMessage(). The other catch block (catch (Exception e)) is optional you can remove it seeing as you are only doing DML in the Try section.

10
  • This will still give the ugly error though since you are just catching it and tossing it back into the addError.
    – dphil
    Dec 9 '14 at 20:09
  • 1
    Not true! Using "getDmlMessage" will give the custom validation message!
    – Force Hero
    Dec 9 '14 at 20:10
  • Ahhh ok. I thought it would give it along with the code error as well. That works then.
    – dphil
    Dec 9 '14 at 20:11
  • Thanks, guys! I have updated my trigger above. Can I just get a confirmation on how how to use try catch keywords with my code given above? Why is there DMLException and Exception being ran? And both are assigned as "e"? Should they be different? Thanks again. Dec 9 '14 at 20:21
  • not super familiar with exception handling yet, sorry :( Dec 9 '14 at 20:22
1

I'm assuming your code goes through your trigger and then updates associated data. Instead of updating the associated data, check the error first and add the error to the parent instead of immediately updating the offending record. This should give you the cleaner error instead of failing on the update inside of your trigger.

Set<Id> accIds = new Set<Id>();
for (Opportunity opp: updatingOpps)
{
  if (String.isBlank(opp.Account.somevalue))
  {
    opp.otherfield.addError('When the Account's Back-up field is set to BUCA, you must make a selection for Back-Up Coverage. If you're editing an Opportunity, please first go to the parent account and select a value for Back-Up Coverage.');
  }else
  {
    accIds.add(opp.AccountId);
  }
}

List<Account> accListToUpdate = [SELECT Stuff__c FROM Account WHERE Id IN :accIds];
for (Account acc: accListToUpdate)
{
  //Do work
}
update accListToUpdate;

Obviously this isn't the exact code but your code likely goes along the same lines.

5
  • This is not the way forward - if you have tonnes of validation rules you'll end up migrating them to code which is not best practice
    – Force Hero
    Dec 9 '14 at 20:13
  • @ForceHero Depends on intentions tbh. They could just as easily decide that they want to default that field to something instead of throwing an error. I still find it a very relevant answer that for some people might be more useful than the generic one depending on scenario.
    – dphil
    Dec 9 '14 at 20:18
  • That is true but seeing as OP already had a validation rule it seemed that was the solution required.
    – Force Hero
    Dec 9 '14 at 20:21
  • @ForceHero Requirements change. Knowing extra options often gives you better ways to make things smooth for the end User. I'm not disagreeing your answer likely fits OP's current needs though.
    – dphil
    Dec 9 '14 at 20:24
  • Yes, fair point :)
    – Force Hero
    Dec 9 '14 at 20:31
0

You could use this to only fail the contact records whose related accounts failed (this code is untested but it should give you the foundation you need!)

trigger primaryLiaisonCount2 on Contact(after update) {
    if(checkRecursive.runOnce()){
        List<Account> accList = new List<account>();
        List<Account> accListToUpdate = new List<account>();
        Set<Account> accSet = new Set<Account>();
        set<id> allAccIds = new Set<id>();    

        for (Contact c1: trigger.new){
            //excluding Marketo, Ken Reyes, and Arnold one line below.
            if (c1.AccountID != null && c1.LastModifiedById != '00500000006wnoE' && c1.LastModifiedById != '00500000006xdMm' && c1.LastModifiedById != '00500000006wsIm'){
                allAccIds.add(c1.AccountID);
            } 
        }


        decimal amount1=[SELECT count() FROM Contact WHERE AccountID IN :allAccIds AND Primary_Liaison__c != null AND Marketing_Status__c != 'Gone From Company'];
        decimal amount2=[SELECT count() FROM Contact WHERE AccountID IN :allAccIds AND Receive_Client_Survey__c = true AND Marketing_Status__c != 'Gone From Company'];
        LIST<Account> acc1=[SELECT ID FROM Account WHERE ID IN :allAccIds AND recordtypeID = '012000000000j8S'];
        // this looks unused? - commented out
        //list<Account> acc3=[SELECT ID From Account WHERE ID NOT IN :allAccIds AND recordtypeID ='012000000000j8S'];

        for (Contact c1: trigger.new){
            for (Account acc2:acc1){
                acc2.Primary_Liaisons__c = amount1;
                acc2.Receive_Client_Surveys__c = amount2;
                accList.add(acc2);           
            }
        }

        accSet.addall(accList);
        accListToUpdate.addall(accSet);



        Database.SaveResult[] sr = Database.update(accListToUpdate, false);

        // Iterate through each returned result
        Integer i = 0;
        for (Database.SaveResult sr : srList) {
            if (sr.isSuccess()) {
                // Operation was successful, so get the ID of the record that was processed
                //System.debug('Successfully inserted account. Account ID: ' + sr.getId());
            }
            else {
                // Operation failed, so get all errors                
                for(Database.Error err : sr.getErrors()) {
                    //System.debug('The following error has occurred.');
                    //System.debug(err.getStatusCode() + ': ' + err.getMessage());

                    // you need to find the contacts associated to the failed account
                    Account failedAcc = accListToUpdate.get(i);

                    // really we should have scoped the contacts into Map<Id, List<Contact>> consByAccId
                    //  but your code is a little crazy and its late ;)
                    for (Contact c1: trigger.new) {
                        if (c1.AccountId == failedAcc.Id) {
                            c1.addError(err.getMessage());
                        }
                    }
                }
            }
            i++;
        }
    }

}
2
  • ah yes, the "old xref an update of a list of ObjectB to the triggered list of ObjectA so the error gets applied to the correct ObjectA in an allOrNothing=false use case" pattern
    – cropredy
    Dec 9 '14 at 23:29
  • @Mark Liu Does this answer resolve your issue?
    – Force Hero
    Dec 11 '14 at 9:01

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.