2

I have a list of tasks that need to be updated. To avoid duplicates, it first gets converted into a Map<Id, Task> whose values are updated as follows:

Map<Id, Task> mapIdTasksToUpdate = new Map<Id, Task>(taskList);
update mapIdTasksToUpdate.values();

The problem is: initializing the map as new Map<Id, Task>(taskList) throws a list exception: 'System.ListException: Row with duplicate Id at index: X'. The number X varies from one execution to another. However, when trying to initialize an empty map and then put row by row manually as:

Map<Id, Task> mapIdTasksToUpdate = new Map<Id, Task>();
for(Task taskToUpdate : taskList)
     mapIdTasksToUpdate.put(taskToUpdate.Id, taskToUpdate);
update mapIdTasksToUpdate.values();

it executes without any exceptions.

What is the difference between the two approaches that makes the system throws the list exception? Why is it complaining about duplicates when maps can't hold any (assuming Id as keys)? Any ideas?

P.S.: I think its worth to mention that the exception is thrown from an Apex Test class. I couldn't reproduce the error otherwise. Additionally, I already made sure (using System.debug) that there is not a duplicate Id being inserted on the map.

I appreciate any help you may give me.

Charles

EDIT: I'm adding how the taskList is created and populated:

public void updateClosedTasks(List<Task> closedTasks) {

   if(closedTasks== null || closedTasks.size() == 0)
      return;

   List<Task> taskList = new List<Task>();

      for(Task taskToUpdate : closedTasks)
      {
         if(taskToUpdate.masterTask__c)
            taskList.add(taskToUpdate );
      }

      if(taskList.size() > 0)
         TaskBO.getInstance().updateTasks(taskList);
}

The method TaskBO.getInstance().updateTasks() does the updating from the map.

6
  • Can you show how taskList is being created? The duplicate Id is there, not in the Map, which as you say cannot contain duplicates.
    – David Reed
    Oct 8 '18 at 18:44
  • 1
    Sure. I will add it to the original question. Oct 8 '18 at 18:48
  • In the version where you're looping through the taskList and inserting into the map one at a time, if there is a task record with the same Id -- go with me here -- then the value for that key would be overwritten. No error expected there. I'm not sure how that would be handled by the first version -- instantiating the map and initializing it with the values of the list at the same time. I was thinking you must have the same task in the list twice, but you said you've checked for that. Hmm. Oct 8 '18 at 18:54
  • That code initializing taskList shouldn't work at all - it's iterating over an empty, newly-created List.
    – David Reed
    Oct 8 '18 at 19:00
  • @DavidReed I'm sorry. There was a typo in the for header. I fix'd it. Oct 8 '18 at 19:14
3

While your code initializing taskList may reveal the underlying (ultimate) problem, we can illustrate the proximate issue with some Anonymous Apex. It has to do not with the update DML or the Map itself, but with the Apex idiom that converts from an sObject List to a Map<Id, sObject>.

This snippet works just fine:

Account a, b;

a = new Account(Id = '001000000000001');
b = new Account(Id = '001000000000002');

Map<Id, Account> acctMap = new Map<Id, Account>(
    new List<Account> { a, b }
);

But if we create your situation synthetically by changing b to have the same Id as a,

Account a, b;

a = new Account(Id = '001000000000001');
b = new Account(Id = '001000000000001');

Map<Id, Account> acctMap = new Map<Id, Account>(
    new List<Account> { a, b }
);

we get back the expected exception:

System.ListException: Row with duplicate Id at index: 1

Apex doesn't know which of the two Accounts should be stored as the value for their shared Id in the resulting Map. There's no inherent precendence between the two. When, however, you manually iterate over the list and add each item to the map in order,

Account a, b;

a = new Account(Id = '001000000000001');
b = new Account(Id = '001000000000001');

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

for (Account i : new List<Account> { a, b }) {
    acctMap.put(i.Id, i);
}

everything is fine, because you're overwriting the previous value for that duplicated Id when you successively call acctMap.put(). In essence, you're establishing that the ordering of the array disambiguates between the duplicates, with later entries winning.

6
  • Additionally, the put() method returns the old value, if you replaced an existing entry that was already in the map.
    – Mark Pond
    Oct 8 '18 at 19:13
  • David, I completely get your point here and agree with it. The problem is that the list in new Map<Id, Task>(taskList) has no duplicated Ids as I could see in the logs. Here is a sample from the last execution: (Task:{Id=00TV000000Cw2MyMAJ}, Task:{Id=00TV000000Cw2MxMAJ}) Oct 8 '18 at 19:33
  • Hmm. That is distinctly weird. Any chance you could distill out a reproducible example that I/we can drop in a developer edition and experiment with?
    – David Reed
    Oct 8 '18 at 19:36
  • Yes, I will try to do that and add to the original question later. Thank you anyways. Just by reading your answer I learned a lot about maps. Oct 8 '18 at 20:01
  • Since you said this only occurs in Test classes, I would bet you put the same record in the list twice, or you have multiple accounts with null IDs. Oct 8 '18 at 20:41
0

What Put method does ?

If the map previously contained a mapping for this key, the old value is returned by put method and then replaced.

What Map(recordList) does ?

It creates a new instance of the Map class and populates it with the passed-in list of sObject records. It throws above duplicate record error.

If you want to do that in one go instead of iterating whole records and calling put method for each record. You can use putAll(sobjectArray) method of map class. Which adds the list of sObject records to a map declared as Map<ID, sObject> or Map<String, sObject>.

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.