2

I wrote a method that works when it is before insert in my trigger handler. A problem I encountered is that the after insert method that already existed failed to run. I cannot figure out why it does not work when one method is before and one method is after so I want to try to run both in after insert.

My method below runs after insert and updates two of the fields from another record. I get this error when I run the test class now:

Record is read only

I know that because it's after the insert the record is locked so I have to do another dml statement to update the record again. I thought I was doing that can you explain why doesn't my code do that?

private void updatePA(Map<Id, Product2> newMap)
{
    List<Product2> prodsToInsert = new List<Product2>();
    List<String> prodsToUpdate = new List<String>();
    Map<String, Accounting_Unit__c> units = new Map<String, Accounting_Unit__c>();

    for(Product2 prod : newMap.values())
    {
        if(prod.Product_LOB__c != null)
        {
            prodsToUpdate.add(prod.Product_LOB__c);
        }
    }

    for (Accounting_Unit__c unit : [SELECT  Id,
                                            Name, 
                                            Practice_Area__c,
                                            Sector__c
                                   FROM Accounting_Unit__c
                                   WHERE Name IN :prodsToUpdate])
    units.put(unit.Name, unit); 

    for (Product2 product : newMap.values())
    {
        Accounting_Unit__c unit = units.get(product.Product_LOB__c);
        if (unit != null)
        {
        product.Product_BU__c = unit.Practice_Area__c;
        product.Product_Sector__c = unit.Sector__c;
        prodsToInsert.add(product);
        }
    }
    update prodsToInsert;
}       
  • Why not move the logic to before insert? – Adrian Larson Sep 26 '16 at 15:00
  • I did, the update doesn't seem to work and says the record is locked in debug log. – Dan Wooding Sep 26 '16 at 15:01
2

The issue is in this for loop

for (Product2 product : newMap.values())
{
    Accounting_Unit__c unit = units.get(product.Product_LOB__c);
    if (unit != null)
    {

        // This is the line that should be giving you the error, because you're attempting
        //   to write to a read-only record.
        product.Product_BU__c = unit.Practice_Area__c;

        // If the above line is commented out, this line would throw the same error
        product.Product_Sector__c = unit.Sector__c;

        prodsToInsert.add(product);
    }
}

product in this context will throw the read-only error if you're passing a trigger context variable into the overall method.

To get around that, you could use the pattern that uses the sObject constructor to set the Id instead of attempting to modify the record directly.

for (Product2 product : newMap.values())
{
    Accounting_Unit__c unit = units.get(product.Product_LOB__c);
    if (unit != null)
    {
        prodsToInsert.add(new Product2(
            Id = product.Id,
            Product_BU__c = unit.Practice_Area__c,
            Product_Sector__c = unit.Sector__c
        ));
    }
}

+edit:

You might also be having trouble getting this to run before insert because the newMap trigger context variable is not available before insert.

It doesn't look like you specifically need trigger.newMap for this, so modifying your method to take a List<Product2> rather than a Map<Id, Product2> might help (and will still be usable if the method must still be callled in an after insert context).

If run before insert, you would remove the call to DML update, and directly modify the records from trigger.new rather than using the sObject constructor pattern.

  • Good point, when I had it as a before I had to use newList as you're right there is no id to use in a map yet. What is the keyword or term that this relates to? Trigger context? I want to know why I'm doing what I'm doing and I struggle because I don't know all the correct terms to do a search through documentation. – Dan Wooding Sep 26 '16 at 15:21
  • 1
    @DanWooding Trigger context means anything that runs directly or indirectly as a result of a trigger. before insert and the like are trigger events, and triggers that are set to run on a certain event are <event name> trigger(s) like a before insert trigger or a more generic after trigger (which may run on multiple after events). – Derek F Sep 26 '16 at 15:32
1

You are still adding the trigger context record to the insert list. You need an entirely new SObject instance. Your naming is also quite confusing with update prodsToInsert...

For example:

prodsToInsert.add(new Product2(
    Id=product.Id,
    Product_BU__c=unit.Practice_Area__c,
    Product_Sector__c=unit.Sector__c
));

You may also want to look at the Apex Developer Guide on Trigger Context Variables:

Variable
newMap

Usage
A map of IDs to the new versions of the sObject records.
This map is only available in before update, after insert, after update, and after undelete triggers.

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.