Take the 2-minute tour ×
Salesforce Stack Exchange is a question and answer site for Salesforce administrators, implementation experts, developers and anybody in-between. It's 100% free, no registration required.

I have a customer's org that is throwing an error once in a while.

ELTON.After_Update_Contact_Values: execution of AfterUpdate
caused by: System.ListException: Duplicate id in list: a36a00000009IEUAA2
Trigger.ELTON.After_Update_Contact_Values: line 65, column 1

I can't actually duplicate the problem but I have e-mails showing it happens in the customer org every few days or so.

I think I know the answer... it is to stop using lists to record which records need to be updated and convert to using maps to ensure a given Equipment__c.Id never gets onto the update list twice.

My problem is maps confuse me (the first step is admitting you have a problem).

I feel really guilty asking this (I am getting y'all to do my work for me) but I think it would be invaluable for me to get this information... perhaps reducing my confusion when it comes to maps...

Could someone be so kind as to take my trigger code below and show me how you would convert it so that lstEQ2 and lstEQ3 are replaced with maps ?

trigger After_Update_Contact_Values on Contact (after update)
{
List<ELTON__Equipment__c> lstEQ=new  List<ELTON__Equipment__c>();
List<ELTON__Equipment__c> lstEQ1=new  List<ELTON__Equipment__c>();
List<ELTON__Equipment__c> lstEQ2=new  List<ELTON__Equipment__c>();
List<ELTON__Equipment__c> lstEQ3=new  List<ELTON__Equipment__c>();

lstEQ = [select id,ELTON__Assigned_Contact__c,ELTON__Assigned_Name__c from ELTON__Equipment__c 
        where ELTON__Assigned_Contact__c=:Trigger.New];
lstEQ1 = [select id,ELTON__Loan_Contact__c,ELTON__Loan_Name__c from ELTON__Equipment__c 
         where ELTON__Loan_Contact__c=:Trigger.New];

  for(Contact Con:Trigger.new)
    {
    if((Con.MailingCity != trigger.oldMap.get(Con.Id).MailingCity) || (Con.MailingCountry != trigger.oldMap.get(Con.Id).MailingCountry) || 
     (Con.MailingPostalCode != trigger.oldMap.get(Con.Id).MailingPostalCode) || (Con.MobilePhone != trigger.oldMap.get(Con.Id).MobilePhone) ||  
     (Con.MailingState != trigger.oldMap.get(Con.Id).MailingState) || (Con.MailingStreet != trigger.oldMap.get(Con.Id).MailingStreet) ||
     (Con.Name != trigger.oldMap.get(Con.Id).Name) || (Con.Phone != trigger.oldMap.get(Con.Id).Phone) ||
     (Con.Email != trigger.oldMap.get(Con.Id).Email) || (Con.OtherPhone != trigger.oldMap.get(Con.Id).OtherPhone) ||     
     (Con.FirstName != trigger.oldMap.get(Con.Id).FirstName) || (Con.LastName != trigger.oldMap.get(Con.Id).LastName))   
     {

     for(ELTON__Equipment__c objEquipment:lstEQ)
     {
         ObjEquipment.ELTON__Assigned_Address__c = Con.MailingStreet;
         // ObjEquipment.ELTON__Assigned_Address__c = (ObjEquipment.ELTON__Assigned_Address__c).replace('null','');                      
         ObjEquipment.ELTON__Assigned_City__c = Con.MailingCity;
         ObjEquipment.ELTON__Assigned_Country__c = Con.MailingCountry;
         ObjEquipment.ELTON__Assigned_Name__c = Con.FirstName + ' ' + Con.LastName;
         ObjEquipment.ELTON__Assigned_State_Province__c = Con.MailingState;
         ObjEquipment.ELTON__Assigned_Zip_Postal_Code__c = Con.MailingPostalCode;
         ObjEquipment.ELTON__Assigned_Phone__c = Con.Phone;
         ObjEquipment.ELTON__Assigned_Other_Phone__c = Con.OtherPhone;         
         ObjEquipment.ELTON__Assigned_Email__c = Con.Email;         
         ObjEquipment.ELTON__Assigned_Mobile__c = Con.MobilePhone;         

         lstEQ2.add(ObjEquipment);
       }

     for(ELTON__Equipment__c objEquipment:lstEQ1)
     {
         ObjEquipment.ELTON__Loan_Address__c = Con.MailingStreet;
         // ObjEquipment.ELTON__Loan_Address__c = (ObjEquipment.ELTON__Loan_Address__c).replace('null','');                      
         ObjEquipment.ELTON__Loan_City__c = Con.MailingCity;
         ObjEquipment.ELTON__Loan_Country__c = Con.MailingCountry;
         ObjEquipment.ELTON__Loan_Name__c = Con.FirstName + ' ' + Con.LastName;
         ObjEquipment.ELTON__Loan_State_Province__c = Con.MailingState;
         ObjEquipment.ELTON__Loan_Zip_Postal_Code__c = Con.MailingPostalCode;
         ObjEquipment.ELTON__Loan_Phone__c = Con.Phone;
         ObjEquipment.ELTON__Loan_Other_Phone__c = Con.OtherPhone;         
         ObjEquipment.ELTON__Loan_Email__c = Con.Email;         
         ObjEquipment.ELTON__Loan_Mobile__c = Con.MobilePhone;         

         lstEQ3.add(ObjEquipment);
       }
     }
   }

   if(lstEQ2.size()>0)
   {
       update lstEQ2;
   }
   if(lstEQ3.size()>0)
   {
       update lstEQ3;
   }

}

UPDATE Jan 15/2013 - Further to Ben's Answer and my comment to that answer

Below is the trigger modified to use a single map. My idea is that the map created at the top contains unique combinations of Contact ID and Equipment ID whether the Equipment is assigned, loaned or both.

My problem now is my continuing confusion with the syntax of maps. The error I am getting is:

Error: Compile Error: Invalid initial value type SOBJECT:ELTON__Equipment__c for LIST<ELTON__Equipment__c> at line 15 column 54 

Line 15 is:

mapEQ.put(eq.ELTON__Assigned_Contact__c, new List<ELTON__Equipment__c>(eq));

Any ideas??

trigger After_Update_Contact_Values on Contact (after update)
{
    List<ELTON__Equipment__c> lstEQ2=new  List<ELTON__Equipment__c>();

    Map<Id, List<ELTON__Equipment__c>> mapEQ = new Map<Id, List<ELTON__Equipment__c>>();

    for (ELTON__Equipment__c eq : [select id, ELTON__Assigned_Contact__c, ELTON__Loan_Contact__c
                    from ELTON__Equipment__c 
                    where 
                    ELTON__Assigned_Contact__c=:Trigger.New
                    OR ELTON__Loan_Contact__c=:Trigger.New]) 
    {
       if (mapEQ.containsKey(eq.ELTON__Assigned_Contact__c) == false) 
       {
            mapEQ.put(eq.ELTON__Assigned_Contact__c, new List<ELTON__Equipment__c>(eq));
       } 
       if (mapEQ.containsKey(eq.ELTON__Loan_Contact__c) == false) 
       {
            mapEQ.put(eq.ELTON__Loan_Contact__c, new List<ELTON__Equipment__c>(eq));
       } 
    }

    for(Contact Con:Trigger.new)
    {
        if((Con.MailingCity != trigger.oldMap.get(Con.Id).MailingCity) || (Con.MailingCountry != trigger.oldMap.get(Con.Id).MailingCountry) || 
         (Con.MailingPostalCode != trigger.oldMap.get(Con.Id).MailingPostalCode) || (Con.MobilePhone != trigger.oldMap.get(Con.Id).MobilePhone) ||  
         (Con.MailingState != trigger.oldMap.get(Con.Id).MailingState) || (Con.MailingStreet != trigger.oldMap.get(Con.Id).MailingStreet) ||
         (Con.Name != trigger.oldMap.get(Con.Id).Name) || (Con.Phone != trigger.oldMap.get(Con.Id).Phone) ||
         (Con.Email != trigger.oldMap.get(Con.Id).Email) || (Con.OtherPhone != trigger.oldMap.get(Con.Id).OtherPhone) ||     
         (Con.FirstName != trigger.oldMap.get(Con.Id).FirstName) || (Con.LastName != trigger.oldMap.get(Con.Id).LastName))   
         {
             if (mapEQ.containsKey(Con.Id)) 
             {
                  for (ELTON__Equipment__c objEquipment : mapEQ.get(Con.Id)) 
                  {
                      if(ObjEquipment.ELTON__Assigned__Contact == Con.Id) 
                      {
                         ObjEquipment.ELTON__Assigned_Address__c = Con.MailingStreet;
                         ObjEquipment.ELTON__Assigned_City__c = Con.MailingCity;
                         ObjEquipment.ELTON__Assigned_Country__c = Con.MailingCountry;
                         ObjEquipment.ELTON__Assigned_Name__c = Con.FirstName + ' ' + Con.LastName;
                         ObjEquipment.ELTON__Assigned_State_Province__c = Con.MailingState;
                         ObjEquipment.ELTON__Assigned_Zip_Postal_Code__c = Con.MailingPostalCode;
                         ObjEquipment.ELTON__Assigned_Phone__c = Con.Phone;
                         ObjEquipment.ELTON__Assigned_Other_Phone__c = Con.OtherPhone;         
                         ObjEquipment.ELTON__Assigned_Email__c = Con.Email;         
                         ObjEquipment.ELTON__Assigned_Mobile__c = Con.MobilePhone;         
                      }
                      else
                      {
                         // assumes this equipment is on the list because it's a loan
                         ObjEquipment.ELTON__Loan_Address__c = Con.MailingStreet;
                         ObjEquipment.ELTON__Loan_City__c = Con.MailingCity;
                         ObjEquipment.ELTON__Loan_Country__c = Con.MailingCountry;
                         ObjEquipment.ELTON__Loan_Name__c = Con.FirstName + ' ' + Con.LastName;
                         ObjEquipment.ELTON__Loan_State_Province__c = Con.MailingState;
                         ObjEquipment.ELTON__Loan_Zip_Postal_Code__c = Con.MailingPostalCode;
                         ObjEquipment.ELTON__Loan_Phone__c = Con.Phone;
                         ObjEquipment.ELTON__Loan_Other_Phone__c = Con.OtherPhone;         
                         ObjEquipment.ELTON__Loan_Email__c = Con.Email;         
                         ObjEquipment.ELTON__Loan_Mobile__c = Con.MobilePhone;         
                      }
                      lstEQ2.add(ObjEquipment);
                 } // for equipment
               } // if containskey
        } // if correct fields changed
    } // for trigger new          
    if(lstEQ2.size()>0)
    {
        update lstEQ2;
    }
} // trigger
share|improve this question
    
What is it exactly that confuses you about maps? –  Lex Jan 11 '13 at 15:32
    
Not sure. What I do know is I googled 'Duplicate ID in list' this morning and I read lots of posts on the subject... none of which could I translate into changes to my code to solve the problem. It's probably all boils down to my severe lack of experience in force.com :-( –  AngiB Jan 11 '13 at 15:38
    
What kind of languages do you have experience in? Maybe we can find an equivalent of the map in a language you know to make it more clear. –  Lex Jan 11 '13 at 15:52
2  
Think of a Map as an enhanced List that provides you with a way to find any item in the list by a key that you define. So rather than looping over a list one item at a time to find something you are looking for, you can use the key to find the exact item you're looking for immediately without looping anything. (Think of all the script statements that can be saved!) The key in a map is unique, which is what prevents duplicate entries. Adding a duplicate replaces the old entry. It may be useful to know that a Map in SalesForce is commonly known in general computing by its alter ego, HashMap. –  Mark Pond Jan 11 '13 at 17:44
    
Per your update: split line 15 into a few statements so that you have a clearer understanding of what is happening with the syntax. 15: mapEQ.put(eq.ELTON__Assigned_Contact__c, new List<ELTON__Equipment__c>()); // create the list within the map 16: List<ELTON__Equipment__c> tempEQList = mapEQ.get(eq.ELTON__Assigned_Contact__c); // retrieve the list by key from the map 17: tempEQList.add(eq); // add your item to the list contained in the map –  Mark Pond Jan 15 '13 at 18:17
show 2 more comments

4 Answers 4

up vote 6 down vote accepted

You can use "sets" instead of lists. They are similar to list but without duplicates.

Update to add:

Personally, i think the fix for your issue to try and avoid using 2 different lists and updates if its for same object. I tried modifying your earlier code to eliminate the same. Please let me know if it works:

trigger After_Update_Contact_Values on Contact (after update)
{
List<ELTON__Equipment__c> lstEQ=new  List<ELTON__Equipment__c>();
//List<ELTON__Equipment__c> lstEQ1=new  List<ELTON__Equipment__c>();
List<ELTON__Equipment__c> lstEQ2=new  List<ELTON__Equipment__c>();
//List<ELTON__Equipment__c> lstEQ3=new  List<ELTON__Equipment__c>();

//lstEQ = [select id,ELTON__Assigned_Contact__c,ELTON__Assigned_Name__c from ELTON__Equipment__c 
//        where ELTON__Assigned_Contact__c=:Trigger.New];
//lstEQ1 = [select id,ELTON__Loan_Contact__c,ELTON__Loan_Name__c from ELTON__Equipment__c 
//        where ELTON__Loan_Contact__c=:Trigger.New];

lstEQ = [select id,ELTON__Assigned_Contact__c,ELTON__Assigned_Name__c from ELTON__Equipment__c,
        ELTON__Loan_Contact__c,ELTON__Loan_Name__c from ELTON__Equipment__c  
        where ELTON__Assigned_Contact__c=:Trigger.New];      

  for(Contact Con:Trigger.new)
    {
    if((Con.MailingCity != trigger.oldMap.get(Con.Id).MailingCity) || (Con.MailingCountry != trigger.oldMap.get(Con.Id).MailingCountry) || 
     (Con.MailingPostalCode != trigger.oldMap.get(Con.Id).MailingPostalCode) || (Con.MobilePhone != trigger.oldMap.get(Con.Id).MobilePhone) ||  
     (Con.MailingState != trigger.oldMap.get(Con.Id).MailingState) || (Con.MailingStreet != trigger.oldMap.get(Con.Id).MailingStreet) ||
     (Con.Name != trigger.oldMap.get(Con.Id).Name) || (Con.Phone != trigger.oldMap.get(Con.Id).Phone) ||
     (Con.Email != trigger.oldMap.get(Con.Id).Email) || (Con.OtherPhone != trigger.oldMap.get(Con.Id).OtherPhone) ||     
     (Con.FirstName != trigger.oldMap.get(Con.Id).FirstName) || (Con.LastName != trigger.oldMap.get(Con.Id).LastName))   
     {

     for(ELTON__Equipment__c objEquipment:lstEQ)
     {
        If(objEquipment.ELTON__Assigned_Contact__c == con.id)
        {
             ObjEquipment.ELTON__Assigned_Address__c = Con.MailingStreet;
             // ObjEquipment.ELTON__Assigned_Address__c = (ObjEquipment.ELTON__Assigned_Address__c).replace('null','');                      
             ObjEquipment.ELTON__Assigned_City__c = Con.MailingCity;
             ObjEquipment.ELTON__Assigned_Country__c = Con.MailingCountry;
             ObjEquipment.ELTON__Assigned_Name__c = Con.FirstName + ' ' + Con.LastName;
             ObjEquipment.ELTON__Assigned_State_Province__c = Con.MailingState;
             ObjEquipment.ELTON__Assigned_Zip_Postal_Code__c = Con.MailingPostalCode;
             ObjEquipment.ELTON__Assigned_Phone__c = Con.Phone;
             ObjEquipment.ELTON__Assigned_Other_Phone__c = Con.OtherPhone;         
             ObjEquipment.ELTON__Assigned_Email__c = Con.Email;         
             ObjEquipment.ELTON__Assigned_Mobile__c = Con.MobilePhone;         
        }
        if(objEquipment.ELTON__Loan_Contact__c == con.id)
        {
             ObjEquipment.ELTON__Loan_Address__c = Con.MailingStreet;
             // ObjEquipment.ELTON__Loan_Address__c = (ObjEquipment.ELTON__Loan_Address__c).replace('null','');                      
             ObjEquipment.ELTON__Loan_City__c = Con.MailingCity;
             ObjEquipment.ELTON__Loan_Country__c = Con.MailingCountry;
             ObjEquipment.ELTON__Loan_Name__c = Con.FirstName + ' ' + Con.LastName;
             ObjEquipment.ELTON__Loan_State_Province__c = Con.MailingState;
             ObjEquipment.ELTON__Loan_Zip_Postal_Code__c = Con.MailingPostalCode;
             ObjEquipment.ELTON__Loan_Phone__c = Con.Phone;
             ObjEquipment.ELTON__Loan_Other_Phone__c = Con.OtherPhone;         
             ObjEquipment.ELTON__Loan_Email__c = Con.Email;         
             ObjEquipment.ELTON__Loan_Mobile__c = Con.MobilePhone;
        }
         lstEQ2.add(ObjEquipment);
      }
     } 
    }
     if(lstEQ2.size()>0)
   {
       update lstEQ2;
   }



}
share|improve this answer
    
That's actually a great idea. –  Lex Jan 11 '13 at 16:14
1  
Be very wary of this. Account(Id=123,Name=Apple) != Account(Id=123,Name=Orange) so you will still have 2 entries in the set and still get the duplicate id error –  Daniel Blackhall Jan 14 '13 at 21:24
    
I think this is going to work - the only change I made is to the LstEQ query to make sure the where clause has the or to make sure Equipment is included where it is assigned OR loaned "where ELTON__Assigned_Contact__c=:Trigger.New OR ELTON__Loan_Contact__c=:Trigger.New". –  AngiB Jan 16 '13 at 15:04
    
Sorry, i forgot to add the "OR" but glad if its works :) –  Sai Jan 16 '13 at 15:47
add comment

I suspect the problem you are encountering is because you essentially query the same object twice, once where contacts are the "assigned contact", and once when the contacts are the "loan contact".

Are you sure those are mutually exclusive queries? If not, by calling update twice on the same record, you might be running into this contention issue since both updates would be called before the commit at the end of the transaction.

But here's a thought. The ELTON_Equipment_c is a child of Contact, right? The cardinality of this relationship would allow you to do cross-object formula fields (One ELTON_Equipment_c record is related to one Contact record). Instead of forcing Apex to do all of this work, why not just add a whole bunch of cross-object formula fields to pull down the information needed from each contact.

So in your ELTON_Equipment__c object add a field for each contact field you want to reference. The fields from the "Assigned" contact would have a formula that looked like this.

ELTON__Assigned_Contact__r.*fieldname*

For the "Loan" contact fields, the formulas would be like this:

ELTON__Loan_Contact__r.*fieldname*

This seems like a clicks-not-code solution to me.

share|improve this answer
    
Any individual ELTON__Equipment__c object can be ASSIGNED to either an Account/Contact/Opportunity or User. It can also be LOANED to an Account/Contact/Opportunity or User. therefore ELTON__Equipment__c has 8 optional lookup relationships to those 4 other objects. The trigger in this issue will update some basic information on the Equipment for either the Assignment or the Loan only for Equipment currently assigned or loaned to that contact. I hope this clarifies what we are trying to do. –  AngiB Jan 13 '13 at 15:24
    
Also I have been UNABLE to duplicate this problem in our Dev environment. I was SURE if I assigned AND loaned a piece of Equipment to the same contact and then went and updated that contact that it would throw the error but alas NO. I simply cannot duplicate the problem. –  AngiB Jan 13 '13 at 15:33
    
This still appears to be solvable with cross-object formula fields, if you ask me. If the requirement is that equipment must always have the current contact information for an assigned contact show up as fields in the equipment; if those values must always be in sync...well that's one of the prime use cases of cross-object formula fields: prevent duplication of data. That way, there isn't a need to make more stuff happen when you save a contact. The formula reads the contact data when the equipment record is accessed. –  Peter Jan 13 '13 at 23:19
add comment

The main issue I see is that you are looping through all contacts, then within that you are looping through your queried ELTON_Equipment_c records without checking to see if the Contact is linked to that specific record. If you update records in batches (either through dataloader, or simply through a list view) you will end up with duplicates.

So, as an example say you have records Contact1 and Contact2 with Contact1 linked to Equipment1 via ELTON__Assigned_Contact_c, and Contact2 linked to Equipment2 via ELTON_Loan_Contact__c. Your logic will be doing the following:

Contact1: Loop around all lstEQ (Equipment1) and add to list. Loop around all lstEQ1 (Equipment2) and add to list.

Contact2: Loop around all lstEQ (Equipment1) and add to list. Loop around all lstEQ1 (Equipment2) and add to list.

So, in your lists you will have Equipment1 twice, and Equipment2 twice.

If you want to fix this you need to put your queried records in maps too:

Map<Id, List<ELTON__Equipment__c>> mapEQ = new Map<Id, List<ELTON__Equipment__c>>();
Map<Id, List<ELTON__Equipment__c>> mapEQ2 = new Map<Id, List<ELTON__Equipment__c>>();

for (ELTON__Equipment__c eq : [select id,ELTON__Assigned_Contact__c,ELTON__Assigned_Name__c 
                from ELTON__Equipment__c 
                where ELTON__Assigned_Contact__c=:Trigger.New]) {
   if (mapEQ.containsKey(eq.ELTON__Assigned_Contact__c) == false) {
      mapEQ.put(eq.ELTON__Assigned_Contact__c, new List<ELTON__Equipment__c>(eq));
   } else {
      mapEQ.get(eq.ELTON__Assigned_Contact__c).add(eq);
   }
}

for (ELTON__Equipment__c eq : [select id,ELTON__Loan_Contact__c,ELTON__Loan_Name__c 
                from ELTON__Equipment__c 
                where ELTON__Loan_Contact__c=:Trigger.New]) {
   if (mapEQ2.containsKey(eq.ELTON__Assigned_Contact__c) == false) {
      mapEQ2.put(eq.ELTON__Loan_Contact__c, new List<ELTON__Equipment__c>(eq));
   } else {
      mapEQ2.get(eq.ELTON__Loan_Contact__c).add(eq);
   }
}

Then in your contact loop you can do the following:

for (Contact c : Trigger.new) {
   ...

   if (mapEQ.containsKey(c.Id)) {
      for (ELTON__Equipment__c eq : mapEQ.get(c.Id)) {
         // do logic
      }
   }

   if (mapEQ2.containsKey(c.Id)) {
      for (ELTON__Equipment__c eq : mapEQ2.get(c.Id)) {
         // do logic
      }
   }
   ...
}

This still won't handle the situation if Loan Contact is the same as Assigned Contact. If you never get this situation you could simply add a validation rule to make sure Loan Contact is not the same as Assigned Contact. If, however, you do want to handle this scenario in code you will have to make sure when looping around the second list check what is already added to the list of updated equipment.

share|improve this answer
    
OK sorry to be a pain but I can't bring myself to solve the problem without solving all the problems. Now the above answer has some key things missing in the //do logic section but what if there is one map that is contact ID's with associated Equipment Ids. In the upper loop make a map of contact IDs and Equipment IDs where ELTON__assigned_contact__c OR ELTON__loan_contact__c equals the given Contact.Id. Then in the lower loop find each of those equipment Ids and do an if to check whether the contact ID equals the assigned and then the loan and update the appropriate fields accordingly. –  AngiB Jan 15 '13 at 15:54
add comment

You should use Set. Set automatically removes duplicate elements.

You can convert List into Set like this -

Set<integer> mySet = new Set<integer>();
mySet.addAll(list) ;
share|improve this answer
    
This is a duplicate of @Sai's answer –  Daniel Blackhall Jan 14 '13 at 21:26
add comment

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.