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;
    }

}
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.

  • is the Map part still needed? – slsfrc Jan 24 '17 at 9:26
  • Not even a little bit if this is all you need to update. – Adrian Larson Jan 24 '17 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 '17 at 9:51
  • will this __r automatically get the updated field value from BlockA__c to BlockB__c ? – slsfrc Jan 24 '17 at 9:52
  • Same pattern as for the field you already update. – Adrian Larson Jan 24 '17 at 9:53

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.