Apex Cast sObject list dynamically to a specific sObject Type
I know this approach is strange because you are still working with a List<SObject>
, but when you assign it you can make it more specific (e.g. List<Account>
) by using Type.forName
and Type.newInstance
methods.
public static void dynamicUpsert(List<SObject> records)
{
Schema.SObjectType sObjectType = records.getSObjectType();
if (sObjectType != null)
{
String listType = 'List<' + sObjectType + '>';
List<SObject> castRecords = (List<SObject>)Type.forName(listType).newInstance();
castRecords.addAll(records);
upsert castRecords;
}
}
The getSObjectType
call may not be completely reliable, especially since some of these records are being inserted and hence won't have ids. For that reason, it is probably better to accept sObjectType
as an additional parameter instead of trying to determine it on the fly.
public static void dynamicUpsert(List<SObject> records, SObjectType sObjectType)
{
String listType = 'List<' + sObjectType + '>';
List<SObject> castRecords = (List<SObject>)Type.forName(listType).newInstance();
castRecords.addAll(records);
upsert castRecords;
}
Update
Some bad news, as discovered here (actually earlier than your post), the above methodology does not always work. Specifically, if I want to perform a partial upsert
and also specify an external Id field, I get a compile fail.
Database.upsert(castRecords, externalIdField); // compiles, throws TypeException
Database.upsert(castRecords, /*allOrNone*/ false); // compiles, throws TypeException
Database.upsert(castRecords, externalIdField, /*allOrNone*/ false); // compile fail
Additional Update
I have a case open with support and it has been escalated to Tier 3. Currently the responses I'm getting are all along the lines of "will update you tomorrow," but I will post here if I get any resolution.
Additional Update
Support claims the three parameter signature failure is WAD. They said if I want them to actually fix it, I need to post on an Idea, so here it is, vote for it!
Tier 3 ... performed some testing and it looks like that we cannot do upsert with List regardless of which of the 3 method signatures you use.
The only difference in behavior seems to be that we block using the 3-arg one at compile time and the 2-arg and 0-arg ones fail at run- time.
Basically for the 3-arg we validate at compile time that you are not passing in a list parameterized with SObject. But for the other 2 we compile it and we allow it to run and check in the call whether the list is generic.
We could make the 3-arg work but it seems like a feature request and based on the behavior of the 0-arg and 2-arg versions we would presumably still enforce that the underlying list isn't generic.
Perhaps following this pattern will point you in the right direction.
In general, all type information is available at runtime. This means that Apex enables casting, that is, a data type of one class can be assigned to a data type of another class, but only if one class is a child of the other class. Use casting when you want to convert an object from one data type to another.
In the following example, CustomReport extends the class Report. Therefore, it is a child of that class. This means that you can use casting to assign objects with the parent data type (Report) to the objects of the child data type (CustomReport).
In the following code block, first, a custom report object is added to a list of report objects. After that, the custom report object is returned as a report object, then is cast back into a custom report object.
Public virtual class Report {
Public class CustomReport extends Report {
// Create a list of report objects
Report[] Reports = new Report[5];
// Create a custom report object
CustomReport a = new CustomReport();
// Because the custom report is a sub class of the Report class,
// you can add the custom report object a to the list of report objects
Reports.add(a);
// The following is not legal, because the compiler does not know that what you are
// returning is a custom report. You must use cast to tell it that you know what
// type you are returning
// CustomReport c = Reports.get(0);
// Instead, get the first item in the list by casting it back to a custom report object
CustomReport c = (CustomReport) Reports.get(0);
}
}