I coudn't replicate the issue from provided example. If you could elaborate I may be able to give you more precise answer, why addAll()
worked for you.
The key here is that the declarion has been with Map<Id, SObject>
. And upsert
does not work on SObject collections (there is a ways arround it).
I was able to reproduce the same behaviour with following code. (I belive you could have something similar at the beginning)
String response = '[{"attributes": {"type": "Account"}, "id": "00100000000000001", "name":"Test"}]';
List<SObject> objs = (List<SObject>)JSON.deserialize(response, List<SObject>.class);
Map<Id, SObject> sObjectMap = new Map<Id, SObject>(objs);
List<Account> acountList = (List<Account>) sObjectMap.values();
upsert acountList;
// Runtime exception: System.TypeException: DML on generic List<SObject> only allowed for insert, update or delete
Simplified example, everyone can try:
List<Account> accounts = (List<Account>) new List<SObject>{
new Account(Name = 'Doe'),
new Account(Name = 'Jackson')
};
- I've created collection variable of type List that should hold Account objects.
- I've actually initialized it as
List<SObject>
.
- But my
List<SObject>
holds Account instances only.
- Apex will let me deploy (and build) the class.
- Remember
accountList
variable actually points to List<SObject>
.
So far so good, you could be doing insert
, update
and delete
DML statements on accountList
.
And if I try upsert
upsert accountList;
// Runtime exception: System.TypeException: DML on generic List<SObject> only allowed for insert, update or delete
Why? Becuase casting List collections actually does nothing. Runtime initialized accountList
as List<SObject>
and does not care that variable pointing to the object says it should be List<Account>
.
Now I will try to explain the confusion behind this.
Casting isn't magic, you are only telling the compiler that Object A is more of a type Object B (reference).
Apex is based on JVM. In Java you can't cast collections (without generics you can't and we don't have generics in Apex).
Not related: In Java you can cast arrays. I don't how Apex List is implemented, but I believe it's rather the ArrayList than simple old Array
What is very confusing is that Apex compiler will let you cast List collections but no Map and Set.
List<Account> accounts = (List<Account>) new List<SObject>();
// Will compile, no runtime exceptions
Map<Id, Account> accountMap = (Map<Id, Account>) new Map<Id, SObject>();
// Will compile, but runtime exception:
// FATAL_ERROR System.TypeException: Invalid conversion from runtime type Map to Map
Set<Account> accountSet = (Set<Account>) new Set<SObject>();
// Won't compile with message:
// Incompatible types since an instance of Set<SObject> is never an instance of Set<Account>
Map<Id, SObject>
?Map<Id, SObject>
. Map.values() should still return as least as List<SObject>, which should upsert fine, right? And why did explicitly casting it as a list of a concrete SObject not work as we expected?List<SObject>
, but I'm not sure why casting to a collection of a concrete SObject type first didn't work. I may play around with that a bit tomorrow.