function readOnly(count){ }
Starting November 20, the site will be set to read-only. On December 4, 2023,
forum discussions will move to the Trailblazer Community.
+ Start a Discussion
Lokeswara ReddyLokeswara Reddy 

Issue with Queueable class if invoked from Trigger.isAfter

Hi All,

I'm under impression that Queueable class, when invoked from trigger, will be treated as a seperate transaction, similar to @future method.
Am I under wrong impression?

I have Queueable class, that will be enqued from Account trigger, where it invokes external webservice and updates few fields in Account.

It works fine when I invoke the Queueable from Trigger.isBefore update context, but it fails when invoked from Trigger.isAfter update !.
Error message : FATAL_ERROR|System.FinalException: Record is read-only

Can some one throw some light on this !?
Amit Chaudhary 8Amit Chaudhary 8

This is because you are in an after insert/update trigger and the records are read only in that context as they have been written, but not committed, to the database.
Unfortunately your trigger is relying on the ids of the records, which means you won't be able to use before insert as the ids won't be populated at that time (as the records haven't been written to the database at that point so while you can write to them, database generated fields aren't populated).
In this instance you'll need to clone the record (or query anew via SOQL) and make changes to the new copy of the record, then execute the update against the new copies.
 
Lokeswara ReddyLokeswara Reddy
@Amit,
Your answer is applicable if the code is trying to update the same set of records from the trigger.
Here, a Queueable class is invoked, which should be treated as a seperate transaction, from the logs I do see a sepearate log is created for Queueable class.
From the log time stamp, I can see that trigger transaction completed before Queueable class invokes webservice class and try to update Account.
I try to debug further to understand the issue, the trigger context is not avaiable from Queueable class, is this an issue with Salesforce?
Caleb SidelCaleb Sidel
@Lokeswara I hope you have solved this already, but I wanted to document what I found to solve this issue in case someone googles this article.

Queueable does support non-primatives so you may be tempted to create your queueable class by passing in say Trigger.new. I did this and kept getting "Read-Only" errors whenever I tried to run an update because my Trigger was "after" context.

So taking a page out of @future I used a list of Ids (primatives) instead and within the constructor I query for the records. This worked.

An overly simplified example:

The trigger:
trigger ContactTrigger on Contact (after insert, after update) 
{
//Only have the trigger fire once (lots of ways to do this, could be criteria this is a simple example
    if(SingleExecution.isAlreadyDone()) return;

//Notice I'm passing in only a Set of contact Ids
    System.enqueueJob(new AsyncContactEmailVerify(Trigger.newMap.keySet()));
 }
The queueable class
public without sharing class AsyncContactEmailVerify implements Queueable, Database.AllowsCallouts 
{
   private List<Contact> contacts; 

   public AsyncContactEmailVerify(Set<Id> contactIds) {
      contacts = [SELECT Id, Email, Email_Status__c FROM Contact WHERE Id IN :contactIds];
   }

   public void execute(QueueableContext context) {

       //REST Callout in this method, modifies contacts passed in
       validateEmailAddresses(contacts);

       //Ensure we don't have an endless loop, prevent the trigger from firing again
       SingleExecution.setAlreadyDone();

       //THIS IS WHERE I kept getting read-only errors until I queried for the contacts
       update contacts;
   }
}