0

I have Active_Contacts__c Decimal field in Account and Active__c checkbox in Contact, also created ContactTrigger and ContactTrigger handler. I have already created logic for after update event and it works properly, but after delete event is not firing and nothing happens, though same code is working in anonymous.

Code below:

trigger ContactTrigger on Contact (after insert, after update, after delete, after undelete) {
   switch on Trigger.operationType {
       when AFTER_INSERT {
           //
       }
       when AFTER_UPDATE {
           ContactTriggerHandler.afterUpdate(Trigger.newMap, Trigger.oldMap);
       }
       when AFTER_DELETE {
           ContactTriggerHandler.afterDelete(Trigger.oldMap);
       }
       when AFTER_UNDELETE {
           //
       }
   }
}
___________________________________________________________________________________

public with sharing class ContactTriggerHandler {
   public static void afterUpdate(Map<Id, SObject> newMap, Map<Id, SObject> oldMap) {
       Contact temp1;
       Contact temp2;
       List<Contact> updatedContact = new List<Contact>();
       for (Id contId : newMap.keySet()) {
           temp1 = (Contact) newMap.get(contId);
           temp2 = (Contact) oldMap.get(contId);
           if (temp1.Active__c != temp2.Active__c) {
               updatedContact.add(temp1);
           }
       }
       if (updatedContact != null) {
           countContactsRelatedToAccsIfUpdate(updatedContact);
       }

   }
   
   public static void afterDelete(Map<Id, SObject> oldMap) {
       countContactsRelatedToAccsIfDel(oldMap);
   }
   
   private static void countContactsRelatedToAccsIfUpdate(List<Contact> contacts) {
       Set<Id> ids = new Set<Id>();
       for (Contact cont : contacts){
               ids.add(cont.AccountId);
       }
       List<Account> accounts = [SELECT Id, Active_Contacts__c, (SELECT Active__c FROM Contacts) FROM Account WHERE Id IN :ids ];
       for(Account acc : accounts){
           for(Contact cont: contacts){
               if(cont.AccountId == acc.Id){
                   if (cont.Active__c == true) {
                       acc.Active_Contacts__c += 1;
                   } else if (cont.Active__c == false) {
                       acc.Active_Contacts__c -= 1;
                   }
               }
           }
       }
       update accounts;
   }

   private static void countContactsRelatedToAccsIfDel(Map<Id, SObject> oldContactsMap) {
       Set<Id> contactsIds = oldContactsMap.keySet();
       for (Contact cont : [SELECT Id FROM Contact]){
           if (contactsIds.contains(cont.Id)){
               contactsIds.remove(cont.Id);
           }
       }
       List<Contact> contacts = [SELECT AccountId, Active__c FROM Contact WHERE Id = :contactsIds];
       List<Account> accounts = [SELECT Active_Contacts__c FROM Account WHERE Id IN (SELECT AccountId FROM Contact WHERE Id = :contactsIds)];
       for (Account acc : accounts){
           for(Contact cont : contacts){
               if(cont.AccountId == acc.Id && cont.Active__c == true){
                   acc.Active_Contacts__c -= 1;
               }
           }
       }
       update accounts;
   }
9
  • Please edit the question to show the actual trigger logic, not just the business logic. Are you sure you have specified after delete in the trigger declaration? Is the deletion a cascade delete or an explicit DML delete?
    – Phil W
    Jan 4 at 21:42
  • @PhilW, added everything else, if you need something else - comment please Jan 4 at 22:08
  • 1) Does this answer your question? ; (2) this line:for (Contact cont : [SELECT Id FROM Contact]){ will retrieve every Contact in the database
    – cropredy
    Jan 4 at 22:21
  • OK, so when you say the delete event is not firing, have you found that it isn't called at all (did you enable debug logs and perhaps add some System.debug statements) or is it that it doesn't do the processing you are expecting? Why are you querying all Contacts at the start of the countContactsRelatedToAccsIfDel? This will go wrong if you have more than 50000 Contacts in your org and seems rather pointless too. If you want to verify that the IDs have really been deleted, use them in the query like SELECT Id FROM Contact WHERE Id IN :contactsIds, though this is overly defensive IMHO.
    – Phil W
    Jan 4 at 22:21
  • @PhilW I checked code in anonymous with System.debug, and results of executing there were visible in org, but i had no response when checked behavior of trigger in org by trying to provoke it fire with deleting contacts in web. I'm querying all contacts to subtract them from trigger.oldmap and get list of Contacts got deleted, so I'll be able to work only with contacts got deleted and accounts related to them, if this is harmful approach - please tell me how to make it more efficient Jan 4 at 22:39

1 Answer 1

1

You can generate one method to do all of this, in far less code:

public static void updateActiveContactCount(
    Contact[] oldRecords, 
    Contact[] newRecords) {
    Map<Id, Account> updatedAccounts = new Map<Id, Account>();
    if(oldRecords != null) {
        for(Contact record: oldRecords) {
            updatedAccounts.put(
                record.AccountId, 
                new Account(Id=record.AccountId,Active_Contacts__c=0)
            );
        } 
    }
    if(newRecords != null) {
        for(Contact record: newRecords) {
            updatedAccounts.put(
                record.AccountId, 
                new Account(Id=record.AccountId,Active_Contacts__c=0)
            );
        }
    }
    for(AggregateResult result: [SELECT COUNT(Id) sum, AccountId Id FROM Contact WHERE AccountId = :updatedAccounts.keySet() AND Active__c = TRUE]) {
        updatedAccounts.get((Id)result.get('Id')).Active_Contacts__c = (Integer)result.get('sum');
    }
    update updatedAccounts.values();
}

You can call this simply as:

ContactTriggerHandler.updateActiveContactCount(Trigger.old, Trigger.new);

And yes, you should trigger this for insert, update, delete, and undelete.

Alternatively, consider just using DLRS. It's built to handle this type of scenario for you automatically with just clicks instead of code.

4
  • Nice answer, though I think there are better options than DLRS these days, such as Rollup. I think it would also be a good idea to explain why you do the new Account(Id=..., Active_Contacts__c=0) into the map since this isn't blindingly obvious to newcomers.
    – Phil W
    Jan 4 at 23:07
  • Thank you for such nice solution! But can you explain how can I put Trigger.old, Trigger.new as parameters if, for example, after delete event have only Trigger.Old according to docs, or I'm not understanding something? Jan 4 at 23:19
  • @Dronchillah The values will be null in some context (e.g. after delete has no Trigger.new), which is why we check each variable for null before iterating.
    – sfdcfox
    Jan 4 at 23:25
  • @sfdcfox Thanks a lot :) Jan 4 at 23:31

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .