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
RDN_LHRRDN_LHR 

Attempt to de-reference a null object

Hi ...

I don't really understand "de-reference a null object" errors and how they're caused.

Every time an Account is updated and the OwnerId value changes, I want all Contacts to get the same OwnerId as the account they're associated to.   Once I get that trigger working, I'll write the trigger on the Contact record as well.

Here is the Account trigger.  What do I change to not get "CANNOT_INSERT_UPDATE_ACTIVATE_ENTITY, accountUpdateContactOwner: execution of AfterUpdate caused by: System.NullPointerException: Attempt to de-reference a null object Trigger.accountUpdateContactOwner: line 21, column 62"


trigger accountUpdateContactOwner on Account (after update) {

Set<Id> AcctIds = new Set<Id>();

    for (Integer i = 0; i < Trigger.new.size(); i++)
    {
        if (Trigger.old[i].OwnerId != Trigger.new[i].OwnerId ) {
            AcctIds.add(Trigger.old[i].Id);
        }
    }
   
Map<Id, Account> AcctOwners = new Map<Id, Account> ([select Id, OwnerId from Account
                 where Id in :AcctIds]);
                

    for (Contact updContacts : [select Id, OwnerId from Contact
                 where AccountId in :AcctIds])
         
    {
        updContacts.OwnerId = AcctOwners.get(updContacts.Id).OwnerId;
        update updContacts;
    }       

}


mtbclimbermtbclimber
Attempt to de-reference a null object is the Apex equivalent of a "Null pointer exception" know to most developers as an "NPE".

The reason you hit this in your code below is because, in pseudo code, your logic goes like this:

1. Create a map of AccountID => Account object from the accounts in the current request where the owner has changed
2. Iterate over ALL the contacts associated to ALL the accounts in the current request
3. For each contact set the ownerId to the owner id of the account that you get from the map by passing in the contact id

A few comments that might help here...

First, you don't need the query to account. If the accounts didn't have the ownerId on them your logic to test for the change would have failed.  Why don't you you just instantiate a new map instead of an ID set and add the accounts to that map directly in your owner change test?

Second and your primary problem,  in your iterator you need to include the accountId in your select and use that value to get the appropriate account from the map.  You are getting the error now because the call to get an account from the map will always return null if you use a contact Id as the key.

Code:
    for (Contact updContacts : [select id, AccountId, OwnerId from Contact
                 where AccountId in :AcctIds])
         
    {
        updContacts.OwnerId = AcctOwners.get(updContacts.AccountId).OwnerId;
        update updContacts;
    }  

 Finally your placement of the update statement within the for loop is going to be problematic as you are going to be updating contacts one at a time here.  Perhaps you were thinking of doing an array-based iterator, i.e. are you missing a loop nest?


RDN_LHRRDN_LHR
Based on the above advice, I now come up with a working trigger.   :smileyvery-happy:   :smileyvery-happy:
 
Here's the reworked version.  I hope I did this nice and efficiently.  My question is around sizing.  This functions nicely in a development org, but will it work in my live org?  We have hundreds of thousands of accounts and some of them have thousands of contacts.  If I go reassign 15000 accounts, will I hit limits using this method, or do the "for" loops take care of this for me? 
 
I have a very bad feeling about that .....
 
----

trigger accountUpdateContactOwner on Account (after update) {

// first Id is Account.Id, second Id Account.OwnerId

Map<Id, Id> AcctOwners = new Map<Id, Id>();
                
 for (Integer i = 0; i < Trigger.new.size(); i++)
 {
  if (Trigger.old[i].OwnerId != Trigger.new[i].OwnerId )
    {
   AcctOwners.put(Trigger.new[i].Id,Trigger.new[i].OwnerId);

    }
 }

          
     for (Contact updContacts : [select Id, AccountId, OwnerId from Contact
         where AccountId in :AcctOwners.keyset() ])
 {
  updContacts.OwnerId = AcctOwners.get(updContacts.AccountId);
  
 }  

}



Message Edited by RDN_LHR on 11-28-2008 04:21 AM

Message Edited by RDN_LHR on 11-28-2008 04:49 AM