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
Zach CarterZach Carter 

Nested map madness

Hi all, I've been working on a fairly complicated trigger this week, and nested maps have been driving me crazy. Here's some background on the trigger :

 

The trigger fires upon a custom s-object being inserted or updated. This custom s-object holds references to two seperate accounts.

 

The point of the trigger is to create another custom s-object (basically a meet up object) to establish a relationship between the two accounts referenced by the object the trigger is firing on. So if the trigger was firing on custom object A which references Accounts A and B, the trigger would ideally create a new custom object C which would also reference accounts A and B.

 

This gets pretty tricky, especially when trying to 'bulkify' the trigger.

 

Here's a step by step description of the approach I took to write this trigger, and the problems I ran into / how I resolved them :

 

if(Trigger.isAfter && (Trigger.isInsert || Trigger.isUpdate)) {      
// List to hold newly created objects to insert List<Account_Partner__c> newAcctPartners = new List<Account_Partner__c>();
// Map to hold Ids of accounts referenced by object(s) trigger is firing on Map<Id,Id> accountIdMap = new Map<Id, Id>();

// Originally this was a Map<Map<Id,Id>, Known_Data_Type__c> but had to be converted to use a key of type String
// Map is used to store relationships between Id pairs and the object they are associated with Map<String, Known_Data_Type__c> kdtAccountMap = new Map<String, Known_Data_Type__c>();

// If the relationship object we are trying to create via the trigger already exists, this map will hold it
// along with whatever object the trigger is firing on, that it relates to Map<Known_Data_Type__c, Account_Partner__c> kdtAccountPartnerMap = new Map<Known_Data_Type__c, Account_Partner__c>();
// Here we iterate over our incoming objects, and if they are valid we store them along with their Ids in the maps we created for(Known_Data_Type__c kdt : Trigger.new) { if((Trigger.isInsert && kdt.Account__c != null && kdt.Data_Provider__c != null) || (Trigger.isUpdate && Trigger.oldMap.get(kdt.Id).Data_Provider__c != kdt.Data_Provider__c)) { accountIdMap.put(kdt.Data_Provider__c, kdt.Account__c); kdtAccountMap.put(kdt.Data_Provider__c + ',' + kdt.Account__c, kdt); kdtAccountPartnerMap.put(kdt, null); } }
// Here we retrieve any relationship objects which already exist - we don't want to establish the relationship twice Map<Id, Account> partnerAccountMap = new Map<Id,Account>([Select Id, Partner_Type__c FROM Account WHERE Id in :accountIdMap.keySet()]);
// I iterate over the retrieved relationship objects and using the map which relates Id pairs to the objects the trigger is firing on
// I fill out another map with the existing relationship object and the trigger object it is related to for(Account_Partner__c acctPartner : [Select Id, Partner_Account__c, Account__c FROM Account_Partner__c WHERE Account__c in :accountIdMap.values() AND Partner_Account__c in :accountIdMap.keySet()]) { String accountPartnerIdString = acctPartner.Partner_Account__c + ',' + acctPartner.Account__c; if(kdtAccountMap.containsKey(accountPartnerIdString)) { kdtAccountPartnerMap.put(kdtAccountMap.get(accountPartnerIdString), acctPartner); } }
// Finally iterate over all the valid objects the trigger is firing on
// If there isn't already a relationship object in existence which relates the two accounts the trigger objects references, then create and insert it for(Known_Data_Type__c kdt : kdtAccountPartnerMap.keySet()) { if(kdtAccountPartnerMap.get(kdt) == null) newAcctPartners.add(new Account_Partner__c(Account__c = kdt.Account__c, Partner_Account__c = kdt.Data_Provider__c, Partner_Role__c = partnerAccountMap.get(kdt.Data_Provider__c).Partner_Type__c)); } insert newAcctPartners; }

 

Hopefully your brain isn't hurting as much as mine was after getting to the end of that Trigger. Yes I know it needs to be split into helper methods, etc...

 

I emboldened, underlined and italicized some of the code above - these are the parts of code I struggled with the most. I had previously tried to use :

 

Map<Map<Id,Id>, Known_Data_Type__c> for the kdtAccountMap object. This didn't work however - it seems that even if the inner Map entries are different, the outer Map will treat them as duplicate keys.

 

So if I try to do something like :
kdtAccountMap.put(new Map<Id,Id> {id1, id2}, kdt1) 
kdtAccountMap.put(new Map<Id,Id> {id3, id4}, kdt1) 

I end up with a map that looks like - {{id3,id4} = kdt1} - where the heck did {id1, id2} go? I guess Apex isn't smart enough to figure out that the Key is actually a different map?

Even more strange was the result of trying to print this nested map to the console using System.debug

 

With only one entry the System.debug call would work fine, but as soon as the second entry had been added to the map the System.debug call would fail and a 'Internal Salesforce.com Error' would be thrown with no additional info.

 

This was so god **bleep** frustrating and I tried numerous work arounds -

 

1. Using a wrapper object as the key :

Class IdPair {

   public Id accountId{get;set;};

   public Id partnerAccountId{get;set;};

 

   public IdPair(Id accountId, Id partnerAccountId)

   {

      this.accountId = accountId;

      this.partnerAccountId = partnerAccountId

   }

}

 

Map<IdPair, Known_Data_Type__c>

 

This also failed and would also error out when trying to print out the map. Possibly because I didn't have a to string method, but it also seemed like the key was getting overwritten even if the IdPair was different from previous IdPairs added to the map.

 

2. Using an array of Ids as the key - same issues as above.

 

Why the heck doesn't this work? I'm probably doing something wrong / stupid but I'm not wasiting any more time on this so for now the hacky string contatenation will have to do.

 

Thanks,

 

- An extremely frustrated and confused Zach

Best Answer chosen by Admin (Salesforce Developers) 
sfdcfoxsfdcfox
The IdPair bit would have worked, if you'd not missed a small detail... you need to implement the functions hashCode and equals. See http://www.salesforce.com/us/developer/docs/apexcode/Content/langCon_apex_collections_maps_keys_userdefined.htm for details. Using maps as a key is probably not supported, because the key would change each time a value was added. In fact, thinking about how it might work hurts my head. However, you could have used Map<Id, Map<Id, Known_Data_Type__c>>, which is a very common design mechanism.

All Answers

sfdcfoxsfdcfox
The IdPair bit would have worked, if you'd not missed a small detail... you need to implement the functions hashCode and equals. See http://www.salesforce.com/us/developer/docs/apexcode/Content/langCon_apex_collections_maps_keys_userdefined.htm for details. Using maps as a key is probably not supported, because the key would change each time a value was added. In fact, thinking about how it might work hurts my head. However, you could have used Map<Id, Map<Id, Known_Data_Type__c>>, which is a very common design mechanism.
This was selected as the best answer
Zach CarterZach Carter

Thanks sfdcfox, that makes a lot of sense.

 

I'm going to go with the wrapper object solution and implement those two methods, I think it will make the code more readable.