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 Jul 27 '17 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. – IllusiveBrian Jul 27 '17 at 19:27
  • Your if statement should be if(records !== Trigger.new || !Trigger.isExecuting || Trigger.isBefore) for performance reasons. – sfdcfox Jul 27 '17 at 19:37
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 Jul 28 '17 at 2:42

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.