Count the number of child records on the each parent record

apextrigger

I'm stuck here and not sure why this code is not working. I want to throw error if the child record (Contact)is more than 5 records per Parent record (Account)

Question: I want to restrict the user to create a child record for every parent record.
say each parent record can have upto 5 child records.

Parent: Account
Child: Contact

I have created a custom field in the Account object to track the number of counts of Contact.

Here is my code:

trigger triggerContact on Contact (before insert, before update, after insert, after update, after delete) { 

    Integer maxChildRecord = 5;
         
    List<Contact> contacts = Trigger.isDelete ? Trigger.old : Trigger.new;
     
    Set<Id> acctIds = new Set<Id>(); 
    
    for (Contact c : contacts) { 
        if (c.AccountId != null) {
            acctIds.add(c.AccountId); 
        }
    } 
    
    Map<Id, Account> accountMap = new Map<Id, Account>(); 

    for (AggregateResult ar : [SELECT AccountId AcctId, Count(id) ContactCount 
                               FROM Contact 
                               WHERE AccountId in: acctIds 
                               GROUP BY AccountId]){
       
        accountMap.put((Id)ar.get('AcctId'), new Account(Id = (Id)ar.get('AcctId'), Contact_Count__c = (Integer)ar.get('ContactCount')));
        
    }
     
     List<Account> acctsToRollup = new List<Account>();
    for(Account account : accountMap.values()) {  
        Integer dbChildCount = (Integer)account.Contact_Count__c;
         
        
        Account a = new Account();
        a.Id =  account.Id;  
        a.Contact_Count__c = (Integer)account.Contact_Count__c;   
         
        if (dbChildCount > maxChildRecord){ 
            account.addError('can not add more child!');
        }else { 
            acctsToRollup.add(a);
        } 
    }

    if (acctsToRollup.size() > 0) {
        update acctsToRollup;
     }

Best Answer

There are couple of logical issues

  1. The addError needs to be on the object's trigger context. In this scenario add the Error on the Account Trigger and not on the contact. In your Contact trigger context, addError won't throw an error unless you add an error on the triggered contact record. I would simply move the addError part of the logic to a before update trigger on the Account.

    trigger triggerContact on Account ( before update) { 
    
     Integer maxChildRecord = 5;
    
    
     for(Account acc: trigger.new) {
        if(dbChildCount > maxChildRecord) {
         acc.addError('can not add more child!'');
       }
     }
    
    }
    

The above logic could also be a simple validation rule.

  1. Make sure your trigger context is after Insert, after update and after delete, and your trigger code logic for contact count should not run on before triggers.

Few code pointers, do not have logic inside Trigger and try to move your logic to a Helper class or adopt a Trigger Framework for better maintainability.

Update

Now since your preference is to keep all the logic on Contact itself here is how I would modify the code,

trigger triggerContact on Contact (before insert, before update, after insert, after update, after delete) { 

Integer maxChildRecord = 5;
     
List<Contact> contacts = Trigger.isDelete ? Trigger.old : Trigger.new;
 
Map<Id, List<Contact>> mapAcctIdContactList = new Map<Id, List<Contact>>();

for(Contact c : contacts) {
    if(String.isNotBlank(c.AccountId)){
        if(!mapAcctIdContactList.containsKey(c.AccountId)) {
            mapAcctIdContactList.put(c.AccountId, new List<Contact>());
        }
        mapAcctIdContactList.get(c.AccountId).add(c);
    }
}

Map<Id, Account> accountMap = new Map<Id, Account>(); 

for (AggregateResult ar : [SELECT AccountId AcctId, Count(id) ContactCount 
                           FROM Contact 
                           WHERE AccountId in: mapAcctIdContactList.keyset() 
                           GROUP BY AccountId]){
   
    accountMap.put((Id)ar.get('AcctId'), new Account(Id = (Id)ar.get('AcctId'), Contact_Count__c = (Integer)ar.get('ContactCount')));
    
}
 
 List<Account> acctsToRollup = new List<Account>();
 for(Account account : accountMap.values()) {  
    Integer dbChildCount = (Integer)account.Contact_Count__c;
     
    Account a = new Account();
    a.Id =  account.Id;  
    a.Contact_Count__c = (Integer)account.Contact_Count__c;   
     
    if (dbChildCount > maxChildRecord){
        for(Contact c: mapAcctIdContactList.get(account.Id) {
            c.addError('can not add more child!');
        }
    }else { 
        acctsToRollup.add(a);
    } 
}

if (acctsToRollup.size() > 0) {
    update acctsToRollup;
 }

Carefully note how I am storing all contacts for the Account that came in through (trigger.new/trigger.old) using the data structure Map<Id, List<Contact>>