0

I am encountering this kind of error:

System.DmlException: Update failed. First exception on row 0 with id [Some Id]; first error: CANNOT_INSERT_UPDATE_ACTIVATE_ENTITY, updateBlockB: execution of AfterUpdate caused by: System.ListException: Duplicate id in list: [Some Id]

Class.BlockController.UpdateBlockBThruBlockA: update bListToUpdate; , column 1

Trigger.updateBlockB: BlockController.UpdateBlockBThruBlockA(Trigger.New);, column 1: []

The apex class and trigger does only one thing: update BlockB__c after update of BlockA__c. The function works as expected, but the problem comes when I run the test class.

Kind of confused with this one.

Any help (suggestions, ideas, even some sample codes fitted with this scenario) would be greatly appreciated.

Apex Class:

public class BlockController {

    public static void UpdateBlockBThruBlockA(List<BlockA__c> newBlockA) {

        Set<Id> blockAIds = new Set<Id>();

        for(BlockA__c ba : newBlockA) {
            blockAIds.add(ba.Id);  
        }

        Map<Id, List<BlockB__c>> blockBListByBlockAId = new Map<Id, List<BlockB__c>>(); 

        for(BlockB__c b : [SELECT Id, BlockA__c, Box__c, Num__c FROM BlockB__c WHERE BlockA__c IN: blockAIds]) {

            if(blockBListByBlockAId.containsKey(b.BlockA__c)) {
                blockBListByBlockAId.get(b.BlockA__c).add(b); 
            }
            else {
                List<BlockB__c> b2 = new List<BlockB__c>();
                b2.add(b);
                blockBListByBlockAId.put(b.BlockA__c,b2);
            }

        }

        List<BlockB__c> bListToUpdate = new List<BlockB__c>();

        for(BlockA__c a : newBlockA) {

            if(blockBListByBlockAId.containsKey(a.Id)) {
                for(BlockB__c b3 : blockBListByBlockAId.get(a.Id)) {
                    if(b3.Box__c == true) {
                        b3.BlockA__c = a.Id;
                        b3.Num__c = a.Num__c;
                        bListToUpdate.add(b3);
                    }
                }
            }

        }

        update bListToUpdate;  

    } 

}

Apex Trigger:

trigger updateBlockB on BlockA__c (after update) {

        if(Trigger.isAfter && Trigger.isUpdate) {
            BlockController.UpdateBlockBThruBlockA(Trigger.New);
        }
}

Test Class:

@isTest  

public class TheTestClass {

 static List<BlockA__c> aList = new List<BlockA__c>();
 static List<BlockA__c> aListToUpdate = new List<BlockA__c>();
 static List<BlockB__c> bList = new List<BlockB__c>();
 static List<BlockB__c> bListToUpdate = new List<BlockB__c>();

  private static testMethod void UpdateBlockBViaBlockA() {

        BlockA__c blocka = new BlockA__c();
        blocka.Box__c = true;
        blocka.Num__c = 5000; 
        aList.add(blocka);
        insert aList;

        BlockB__c blockb = new BlockB__c();
        blockb.Box__c = true;
        blockb.BlockA__c = blocka.Id;
        blockb.Num__c = 5000;
        bList.add(blockb);
        insert bList;

        for( BlockA__c a2 : aList) {
            a2.Num__c = 7000;
            aListToUpdate.add(a2);
        }

        update  aListToUpdate;

        Test.startTest();

        for(BlockB__c b : bList) {
            b.Num__c = 7000;
            bListToUpdate.add(b);
        }

        update bListToUpdate;

        BlockController bc = new BlockController();

        Test.stopTest();

  }

}

Updated Apex Class:

public class BlockController {

    public static void UpdateBlockBThruBlockA(List<BlockA__c> newBlockA) {

        Set<Id> blockAIds = new Set<Id>();

        for(BlockA__c ba : newBlockA) {
            blockAIds.add(ba.Id);  
        }

        List<BlockB__c> records = [SELECT BlockA__c, BlockA__r.Num__c, BlockA__r.Num__c FROM BlockB__c WHERE BlockA__c IN :blockAIds AND Box__c = true];

        for(BlockB__c b3 : records) {        
               record.Num__c = record.BlockA__r.Num__c
        }

        update records;
    }

}

1 Answer 1

2

You can avoid all this looping and entirely eliminate your risk of duplicates with two tweaks:

  • Add a WHERE filter to your query: Box__c = true.
  • Use a Right Outer Join to pull in parent data in your SELECT clause.
    • It's not really necessary to set the BlockA__c value, since you're not changing it here.

Then you can just assign to a List, loop through, and update.

List<BlockB__c> records = [
    SELECT BlockA__c, BlockA__r.Num__c FROM BlockB__c
    WHERE BlockA__c IN :blockAIds AND Box__c = true
];
for (BlockB__c record : records)
    record.Num__c = record.BlockA__r.Num__c;
update records;

I recommend you look into error handling and trigger handler patterns, but those subjects are far outside the scope of this question.

8
  • is the Map part still needed?
    – slsfrc
    Jan 24, 2017 at 9:26
  • Not even a little bit if this is all you need to update.
    – Adrian Larson
    Jan 24, 2017 at 9:27
  • Just a question on the update part, let's say BlockA__c fields have been updated, through your approach how can I map recently updated BlockA__c field values to BlockB__c field values?
    – slsfrc
    Jan 24, 2017 at 9:51
  • will this __r automatically get the updated field value from BlockA__c to BlockB__c ?
    – slsfrc
    Jan 24, 2017 at 9:52
  • Same pattern as for the field you already update.
    – Adrian Larson
    Jan 24, 2017 at 9:53

You must log in to answer this question.

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