0

I have Written Following Trigger but getting an Error.

trigger CountContact on Account (after insert, after update) {

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

if (Trigger.isInsert)
{
    for(Account acc : Trigger.New)
    { 
             List<AggregateResult>  cnt= [SELECT COUNT(Id) countTask FROM CONTACT WHERE Contact.AccountId =: acc.id];  
             Integer tskCount = (Integer)cnt[0].get('countTask'); 
             acc.Count_Contact__c = tskCount;
             acc1 .add(acc);
    }
}


if (Trigger.isUpdate)
{
    for(Account accN : Trigger.Old)
    {
        List<AggregateResult>  cnt= [SELECT COUNT(Id) countTask FROM CONTACT     WHERE Contact.AccountId =: accN.id];  
        Integer tskCount = (Integer)cnt[0].get('countTask'); 
        accN.Count_Contact__c = tskCount;
        acc1.add(accN);
     }
}
update acc1;
}

Error is :

System.FinalException: Record is read-only: Trigger.CountContact: line 11, column 1

  • 1
    You need to write a trigger on Contact object, instead of Account object. Also need to consider, insert and delete event of Contact. – Devendra Aug 26 '16 at 11:56
  • Also need to consider undelete event, as user can try to undelete record. – Devendra Aug 26 '16 at 12:57
  • If i created the Trigger on Contact instead of Account, how i will be able to count number of contacts related to a particular Account. Because that count i want in my Accounts Custom Field called Count_Contact__c. – Amol Jadhav Aug 26 '16 at 13:06
  • 1
    Whenever Contact is inserted (After insert event), you need to query all the Contacts associated with the AccountId field of the inserted Contact. And then need to update the respective Account record with the new Count. – Devendra Aug 26 '16 at 13:13
2

You need to write the Trigger on Contact, because if a new Contact is created or deleted, the change in count must be reflected in the Parent Account.

You can try out this:

trigger CountContactOnAccount on Contact (after INSERT, after UPDATE, after DELETE ) {
Set <Id> accountIds = new Set <Id>();
List <Account> lstAccountsToUpdate = new List <Account>();
 if(Trigger.isInsert){
    for(Contact con:trigger.new){
        accountIds.add(con.accountID);
    }
}
if(Trigger.isUpdate|| Trigger.isDelete){
    for(Contact con:trigger.old){
        accountIds.add(con.accountID);
    }
}

for(Account acc:[SELECT Id,Name,Count_Contact__c,(Select Id from Contacts) from Account where Id IN: accountIds]){
    Account accObj = new Account ();
    accObj.Id = acc.Id;
    accObj.Count_Contact__c = acc.Contacts.size();
    lstAccountsToUpdate.add(accObj);
}

UPDATE lstAccountsToUpdate;

}

  • You should also add after UNDELETE to the list of Trigger actions. – dBeltowski Aug 26 '16 at 14:12
  • Thanx its really helpfull. It sorted out my issue.. – Amol Jadhav Aug 26 '16 at 14:17
  • @AmolJadhav You should also consider after UNDELETE because if somebody undeletes a Contact this will affect your count. – dBeltowski Aug 26 '16 at 14:23
0

Consider writing a trigger on Contact rather than Account:

  • On insert you should simply increment that custom field
  • On delete you should decrement it
  • On update you should decrement it from the old account and increment on the new one

Here's a tiny piece of code for the insert:

Map<Id, Account> accMap = new Map<Id, Account>([SELECT Id, Count_Contact__c FROM Account WHERE Id IN :yourListOfAccountIdsThatYouShouldCreate]);
for (Contact c : contactList) {
    accMap.get(c.AccountId).Count_Contact__c++;
}
update accMap;

Similarly, you can write a method for delete and update.

NOTE: make sure that there is a value in Count_Contact__c, otherwise you will most probably get a NullPointerException.


And now a few words about your code:

  1. Never put a query inside a loop. You will hit governor limits before you can say "WTF?"
  2. System.FinalException: Record is read-only: Trigger.CountContact means that you can not modify this record anymore. If you want to modify it, use before triggers or make a copy of desired record(s) and then update it. But you can not change anything in Trigger.new in an after event
  3. Your insert and update parts do exactly same thing, remove one of them. Duplicate code should be avoided at all costs, that will make maintaining a lot easier
  4. Also, I don't think that an account will have any contacts linked to it after you create it, so your after insert doesn't actually do anything
  5. Did I already mention governor limits and queries inside loops?
0

There are few things that I think you should consider to change/do.

1.Your logic should be - 'Every time a new contact is Inserted or Updated or Deleted or UnDeleted, you should go to that contact's parent Account record and calculate all the contact records within this account (including newly inserted/Updated/deleted/UnDeleted contact)' Right ?

Conclusion -

a.The trigger should be written on 'Contact' Object not 'Account'. b. In your query I see that there is a field naming countTask present in contact object (that you must have created in there) , but that is not required. (reread the logic, if any doubt).

2.Now about the Error that you are getting

Your records are already locked after insert and that is why this error is occurring. :)

Explanation : See in the code that you have written here, you are performing logic on account records AFTER INSERT/UPDATE/DELETE , that is the records are already inserted into Database but are not committed (until it clears all the logic under all the triggers written for 'After Insert/Update/Delete' on this object and until then THE RECORDS ARE LOCKED FOR ANY CHANGES ).

Solution to this error Change the event of trigger from 'AFTER INSERT/UPDATE/DELETE' to 'BEFORE INSERT/UPDATE/DELETE' and the bug will get fixed. :)

Happy learning SFDC :) .

Please mark it as correct answer if it helped you . :)

  • Rajeev :)
  • 1. not 'After update' as the number of contact within a parent account will not change on updating a contact - I disagree. If you update Account field on contact, the number of contacts linked to that account will change. 2. Change the event of trigger from 'AFTER INSERT/UPDATE' to 'BEFORE INSERT/UPDATE' and the bug will get fixed. :) - no it won't. Well, it will, but that will cause another error. Id in a before insert trigger is null and OP is querying based on it – Novarg Aug 26 '16 at 13:47
  • @Novarg Right :). Let me make the change right away . :) Thank you for bringing this what I missed to ponder. – Rajeev Aug 26 '16 at 13:49
  • You should also add UNDELETE to the Trigger actions that will cause recalculation. Also this Trigger should be an AFTER on Contact since OP is updating the Account. – dBeltowski Aug 26 '16 at 14:10
  • @DougB does your comment refer to what I posted ? – Rajeev Aug 26 '16 at 14:15
  • Yes. You stated in your Solution to change to BEFORE and INSERT/UPDATE/DELETE. OP should also consider UNDELETE as this will change the count also. Finally it should be an AFTER Trigger since OP is wanting to update Account. – dBeltowski Aug 26 '16 at 14:20
0

Here is a code that may help

trigger NumberOfContacts on Contact (after insert, after delete, after undelete) {


    set<id> accIds = new Set<id>();
    set<id> oldaccIds = new Set<id>();
    List<id> conIds = new List<id>();
    List<Account> accountList = new List<Account>(); 
    List<Account> oldAccountList = new List<Account>(); 
    Integer count = 0;
    if ((trigger.isInsert || trigger.isUndelete) && trigger.isAfter) {
        for ( Contact con : trigger.New) {
            if (con.AccountID != null) {
                accIds.add(con.AccountID);
                conIds.add(con.Id);
            }
        }
        accountList = NumberOfContactsTriggerHandler.getAcocuntDetails(accIds, conIds);
    }


    if (trigger.isDelete && trigger.isAfter) {
        for (Contact con : trigger.Old) {
            if (con.AccountID != null) {
                accIds.add(con.AccountID);
                conIds.add(con.Id);
            }
        }
        accountList = NumberOfContactsTriggerHandler.getAcocuntDetails(accIds, conIds);
    }
NumberOfContactsTriggerHandler.updateNumberOfContactLinked(accountList);
}

public class NumberOfContactsTriggerHandler {

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

    public static List<Account> getAcocuntDetails(set<id> accountSet, List<id> contactList ) {
        return [SELECT ID, No_of_Contacts_Linked__c , (SELECT ID FROM Contacts where id IN :contactList) contacts FROM Account where id in :accountSet];

    }

    public static void updateNumberOfContactLinked(List<Account> accList) {
        for (Account act : accList) {
            integer contactSize = 0;
            System.debug('acclist' + acclist);
            if (trigger.isInsert || trigger.isUndelete) {
                System.debug('acclist' + acclist);
                System.debug('Contacts from Trigger.new' + act.contacts);
                contactSize += act.contacts.size();
                System.debug('act' + act);
                act.No_of_Contacts_Linked__c += contactSize ;
            }
            if (trigger.isDelete) {
                System.debug('acclist' + acclist);
                contactSize += act.contacts.size();
                System.debug('acclist' + act);
                act.No_of_Contacts_Linked__c -= contactSize ;
            }
        }

        System.debug('acclist' + acclist);
        update accList;
    }
}
}

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.