5

Question - How can I determine that an sObject is read-only (due to being part of Trigger.New in an after trigger or otherwise).

Explaination - I've been making a method to try and generically get lookup records for a list of records and put them on correct sObjects in the list (as if I had done an individual SOQL query for each one), but I'm running into an issue when the method is run in an after trigger - System.FinalException: Record is read-only. The issue is that the records in Trigger.New are read-only in after triggers, and apparently this also applies to the putsObject method. That's somewhat OK for me, since my method also returns a Map<ID, sObject> with all the lookup records which can be used instead, but my issue is that FinalException apparently is uncatchable, so I need to determine whether the sObjects in the list are read-only before attempting to use putsObject. For reference, here is the method:

public static Map<ID, sObject> populateRelatedLookup(List<sObject> records, string field, string relatedSObject, List<String> relatedObjectFields)
{
    Set<String> relatedRecords = new Set<String>();
    for (sObject i : records)
    {
        relatedRecords.add((String)i.get(field));
    }
    Map<ID, sObject> relatedMap = new Map<ID, sObject>(Database.query('select ' + String.join(relatedObjectFields, ',') + ' from ' + relatedSObject + ' where ID in :relatedRecords'));
    string fieldAsReference = field.replace('__c', '__r');
    Map<ID, sObject> returnMap = new Map<ID, sObject>();
    for(sObject i : records)
    {
        sObject record = relatedMap.get((String)i.get(field));
        i.putSObject(fieldAsReference, record); // <------ Issue line
        returnMap.put(i.id, record);
    }
    return returnMap;
}

For now, I've changed the code to check whether we are attempting to modify a variable in Trigger.New in an after trigger, but this only works if the user is actually passing Trigger.New to the method literally:

public static Map<ID, sObject> populateRelatedLookup(List<sObject> records, string field, string relatedSObject, List<String> relatedObjectFields)
{
    Set<String> relatedRecords = new Set<String>();
    for (sObject i : records)
    {
        relatedRecords.add((String)i.get(field));
    }
    Map<ID, sObject> relatedMap = new Map<ID, sObject>(Database.query('select ' + String.join(relatedObjectFields, ',') + ' from ' + relatedSObject + ' where ID in :relatedRecords'));
    string fieldAsReference = field.replace('__c', '__r');
    Map<ID, sObject> returnMap = new Map<ID, sObject>();
    for(sObject i : records)
    {
        sObject record = relatedMap.get((String)i.get(field));
        if(!Trigger.isExecuting || records != Trigger.New || Trigger.isBefore)
        {
            i.putSObject(fieldAsReference, record);
        }
        returnMap.put(i.id, record);
    }
    return returnMap;
}
3
  • Just don't call the method from an after context? Why is that not an option?
    – Adrian Larson
    Commented Jul 27, 2017 at 19:25
  • @AdrianLarson Well, the original point of the method was to make it easier to bulkify triggers, so I'd like to be able to use it in triggers. For now I'm working around it using a deep copy of Trigger.New. Commented Jul 27, 2017 at 19:27
  • Your if statement should be if(records !== Trigger.new || !Trigger.isExecuting || Trigger.isBefore) for performance reasons.
    – sfdcfox
    Commented Jul 27, 2017 at 19:37

1 Answer 1

4

There's no way to check if a record is read-only. FinalException is a fatal exception, as if it were a governor limit. It's the future developer's responsibility to not pass you Trigger.new or Trigger.old.

I do know that the read-only flag is a private Boolean value in the SObject, but it can't be exposed or manipulated (at least, not based on any experiments I've tried).

Your best bet is to always clone the list if you want to be bulletproof, but that has performance implications. I'd just as soon leave this alone and document the limitation.

1
  • 1
    Private flag? Probably serializable then! Not that the pearl is likely worth the dive.
    – Adrian Larson
    Commented Jul 28, 2017 at 2:42

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .