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>? – Derek F Apr 09 '19 at 20:09Map<Id, SObject>. Map.values() should still return as least as ListList<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. – Derek F Apr 09 '19 at 21:44