2

I've run into a pretty maddening issue. The simple batch class below looks up certain campaign members and deletes them. It works fine, but when I comment out the debug statement on line 29, the delete operation does not occur.

I've tested it dozens of times, did my very best to ensure that all other variables are kept constant, and I'm still running into the same issue. At this point I'm preparing to deploy with the debug call still there, but if anyone has any ideas, I'd be very curious what's going on.

global class HustleCampaignDeleteBatchable implements Database.Batchable<SObject>, Schedulable {

    //Create list to store campaign ids
    global List<String> hustleCampaigns = new List<String>();
    global List<CampaignMember> campaignMembersToDelete = new List<CampaignMember>();

    global void execute(SchedulableContext sc) {
        Database.executeBatch(this);
    }

    global HustleCampaignDeleteBatchable() {
        //Query for HustleGroupInterest Custom Metadata Type
        Hustle_Group_Update__mdt[] hustleGroupUpdate = [SELECT Campaign_Id__c FROM Hustle_Group_Update__mdt];

        //Populate Hustle Campaign List
        for(Hustle_Group_Update__mdt hgu : hustleGroupUpdate) {
            hustleCampaigns.add(hgu.Campaign_Id__c);
        }

        //System.debug('hustleCampaigns: ' + hustleCampaigns);
    }

    global Database.QueryLocator start(Database.BatchableContext context) {
        //Select all Campaign Members in Hustle campaigns
        return Database.getQueryLocator([SELECT Id, CampaignId FROM CampaignMember WHERE CampaignId IN :hustleCampaigns]);
    }

    global void execute(Database.BatchableContext context, List<CampaignMember> scope) {

        //Loop through all campaign members and add those in a Hustle campaign to a delete list

        for(CampaignMember cm : scope) {
            if (hustleCampaigns.contains(cm.CampaignId)){
                campaignMembersToDelete.add(cm);
            }
        }

        delete campaignMembersToDelete;
    }

    global void finish(Database.BatchableContext context) {
        //Call the batch process to repopulate Hustle Campaigns
        //Database.executeBatch(new HustleCampaignUpdateBatchable());
    }
}

1 Answer 1

6

I believe it's because you're using the string data type instead of the Id data type, and thus triggering some sort of error in the system that debugging fixes (e.g. because they are then identified as real Id values). Note that it's a terrible idea to deploy logic that only works when debugging, because that means it is likely to fail in production when you're not looking.

Ultimately, I'd suggest something closer to this:

global class HustleCampaignDeleteBatchable implements Database.Batchable<SObject>, Schedulable {
    global void execute(SchedulableContext sc) {
        Database.executeBatch(this);
    }

    global Database.QueryLocator start(Database.BatchableContext context) {
        Id[] campaignIds = new Id[0];
        //Populate Hustle Campaign List
        for(Hustle_Group_Update__mdt hgu : [SELECT Campaign_Id__c FROM Hustle_Group_Update__mdt]) {
            campaignIds.add(hgu.Campaign_Id__c);
        }
        //Select all Campaign Members in Hustle campaigns
        return Database.getQueryLocator([SELECT Id FROM CampaignMember WHERE CampaignId IN :campaignIds]);
    }

    global void execute(Database.BatchableContext context, List<CampaignMember> scope) {
        delete scope;
    }

    global void finish(Database.BatchableContext context) {
        //Call the batch process to repopulate Hustle Campaigns
        //Database.executeBatch(new HustleCampaignUpdateBatchable());
    }
}

We remove the global variables, remove the constructor, check the metadata records during the start method, and remove the unnecessary logic. Note that it is not a compiler error to assign a String to an Id, but the Id conversion process will occur at that time and throw an exception if it's an invalid (e.g. not 15/18 characters, etc) Id.

Also note that I specifically avoid setting global variables. Your design "bakes" the Campaign Id values in to your class at the moment you schedule your code; any changes to the metadata will not register until you re-schedule the class. To avoid this, we check it at the moment the batch executes.

4
  • Thank you so much, this is enormously illuminating. Just curious, how how often have you seen situation where a debug call alters the behavior of a class?
    – Justin
    Commented Jul 14, 2018 at 18:39
  • @Justin It's happened a handful of times. It has to do with HashMap and how it stores values. If the hashCode is altered after storage, it results in buggy behavior. System.debug recalculates the hashCode for each element, restoring order back in to the collection. Most built-in types are okay, but Id can sometimes misbehave, and it's possible to write your own implementations of hashCode/equals that violate the rules, thus triggering a "debug" glitch. For now, using the method I specified should be safe for your purposes, but you might want to research it more.
    – sfdcfox
    Commented Jul 14, 2018 at 19:09
  • @Justin I wrote a contrived example as a gist that demonstrates how you can "accidentally" end up changing the hashCode, and demonstrates how System.debug fixes it. Try it out for yourself.
    – sfdcfox
    Commented Jul 14, 2018 at 19:14
  • Super helpful, will definitely spend time learning more on this.
    – Justin
    Commented Jul 16, 2018 at 2:42

You must log in to answer this question.

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