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
Diane ClaytonDiane Clayton 

Really stuck on trigger challenge

Hi
I'm trying to do the Understanding Execution Context module challenge and I really can't see why this won't work. The following code says
System.DmlException: Insert failed. First exception on row 0; first error: CANNOT_INSERT_UPDATE_ACTIVATE_ENTITY, AccountTrigger: execution of BeforeInsert caused by: System.SObjectException: DML statement cannot operate on trigger.new or trigger.old Class.AccountTriggerHandler.CreateAccounts:

trigger AccountTrigger on Account (before insert) { 
     if ( Trigger.isBefore && Trigger.isInsert)
     {
              AccountTriggerHandler.CreateAccounts(Trigger.new);
     }
    }

public class AccountTriggerHandler {
    public static void CreateAccounts(List<Account> Accnts)
    {
         List<Account> ValidAccounts = new List<Account>();
    
            for (Account a : Accnts) {
               a.ShippingState = a.billingState;
               
                ValidAccounts.add(a);
               
            }
    
            if (ValidAccounts.size() > 0) {
                insert ValidAccounts;
            }
    }
}
Best Answer chosen by Diane Clayton
Rajiv Penagonda 12Rajiv Penagonda 12
correction: 1. Changed the trigger to after insert

All Answers

Rajiv Penagonda 12Rajiv Penagonda 12
Diane, your code is running in "before" mode. Meaning that the record is not committed to database yet. Technically that means that you cannot insert the same record twice (first from outside the trigger and again from within the class AccountTriggerHandler)

I have updated your code with a fix. See if it works
trigger AccountTrigger on Account (after insert) { 
     if ( Trigger.isBefore && Trigger.isInsert)
     {
              AccountTriggerHandler.CreateAccounts(Trigger.new);
     }
    }

public class AccountTriggerHandler {
    public static void CreateAccounts(List<Account> Accnts)
    {
         List<Account> ValidAccounts = new List<Account>();
    
            for (Account a : Accnts) {
                ValidAccounts.add(new Account(id=a.id, ShippingState = a.billingState));
            }
    
            if (ValidAccounts.size() > 0) {
                update ValidAccounts;
            }
    }
}

What I did was:

1. Changed the trigger to after update
2. Made sure that during committing back the "ShippingState = billingState" change to database, do an update instead of an insert.

After trigger runs after the record is saved (but not committed) to the database, so update is valid.

If this worked, mark this as best answer.
Rajiv Penagonda 12Rajiv Penagonda 12
correction: 1. Changed the trigger to after insert
This was selected as the best answer
Diane ClaytonDiane Clayton
Thanks Rajiv. I changed accpuntTriggerHandler slightly to set the billingstate of the original object
public class AccountTriggerHandler {
    public static void CreateAccounts(List<Account> Accnts)
    {
         List<Account> ValidAccounts = new List<Account>();
   
            for (Account a : Accnts) {
               ValidAccounts.add(new Account(id=a.id, ShippingState = a.billingState,billingState = a.billingState));
            }
   
            if (ValidAccounts.size() > 0) {
                update ValidAccounts;
            }
    }
}

but my unit test still didn't work so I also changed trigger handler to  if (Trigger.isAfter && Trigger.isInsert) then it passed the unti tests but the challenge   wouldn't validate as it was looking for isBefore so I added this to keep that happy
  if (Trigger.isBefore || Trigger.isAfter && Trigger.isInsert)

Anyway unit tests and challenge completed now. thanks
Kees de KoningKees de Koning
Although the above works to get you through the challenge, there is a simpler solution that satisfies the challenge:
Write an Apex trigger that modifies Account fields before inserting records.
Write an Apex trigger that fires before records are inserted and ensures that the ShippingState field has the same value as the BillingState field.

Basically, your trigger should only modify the Account records, not insert them. That is done automagically afterwards.

trigger AccountTrigger on Account (before insert) 
{
    if (Trigger.isBefore && Trigger.isInsert) {
            AccountTriggerHandler.CreateAccounts(Trigger.new);
    }    
}

public class AccountTriggerHandler {
    public static void CreateAccounts(List<Account> accts) {
        for (Account a : accts) {
            a.ShippingState = a.BillingState;
        }
    }
}
Vijaya Kumar KotagiriVijaya Kumar Kotagiri
For your problem, i tried the below solution, it will work as expected.

public with sharing class AccountTriggerHandler {

    public static void CreateAccounts(List<Account> accnts){               
        for(Integer i=0;i<accnts.size();i++){
            accnts.get(i).ShippingState = accnts.get(i).BillingState;
        }
    }
}

trigger AccountTrigger on Account (before insert) {
    if(trigger.isBefore && trigger.isInsert){
        AccountTriggerHandler.CreateAccounts(trigger.New);
    }
}

@isTest
public class AccountTriggerTest {
    @isTest
    static void TestCreateAccountInBulk(){
        List<Account> accntList = new List<Account>();
        for(Integer i=0;i<200;i++){
            Account account = new Account(Name='Test Accnt '+ i,BillingState='CA');
            accntList.add(account);
        }
        Test.startTest();
        insert accntList;
        Test.stopTest();
        
        List<Account> verifyAccnt = [SELECT Id FROM Account WHERE ShippingState='CA' AND BillingState='CA'];
        System.assertEquals(200, verifyAccnt.size());
    }
}
Peter MinetreePeter Minetree
Thank you Vijaya!
Huma PerweenHuma Perween
It worked perfectly. Thank you Vijaya!
Vishnu SanthoshVishnu Santhosh

I was getting this error because the Name field was missing in Accounts created in the test class:

@isTest
public class AccountTriggerTest {
    
    @isTest static void test1(){
        List<Account> accountsList = new List<Account>();
        for(Integer i=0;i<200;i++){
            Account acc = new Account();
            acc.Name = 'TestAccount '+i;
            acc.BillingState = 'CA';
            accountsList.add(acc);
        }
        
        Test.startTest();
        insert accountsList;
        Test.stopTest();
        
        List<Account> retrievedAccounts = [SELECT Id from Account where ShippingState='CA'];
        
        System.assertEquals(200,retrievedAccounts.size(),'The returned data is not correct');
    }
}

After adding the 'Name' field, the test class ran successfully and I completed the challenge.