[SalesForce] Upserting campaign members for campaigns

I have an apex class that creates campaigns based on mailings and then is supposed to add campaign members to the campaigns that were just created. The last step is done essentially by creating new Campaign Member objects with the campaign id and the mailing contact id they are associated with and then doing an upsert using the entire list of newly created campaign member objects.

For some reason, when I run the upsert while either implicitly or explicitly using the external id of the campaign member id, none of the objects are being inserted. While testing, I was able to see that immediately after a new campaign object is instantiated, it has a null value for the id field even though when the CampaignMember object is queried, all existing objects seem to have a randomly generated id. This could possibly be because the id might not actually get generated until after the insert operation is done.

If this is the case, should another field from the Campaign Member objects be used to do the upsert?

The code for the class method for adding campaign members is below:

 public  void addCampaignMembers(){
        /*Add campaign members to mailings for contact
        */
        //Test campaign objects
        Campaign cm1 = new Campaign( Campaign_Key__c = 'key1', name = 'name1',Mailing_ID__c= 'mid1',Type = 'Content / White Paper');  
        Campaign cm2 = new Campaign(Campaign_Key__c = 'key2', name = 'name2',Mailing_ID__c= 'mid2',Type = 'Content / White Paper');  
        Campaign cm3 = new Campaign(Campaign_Key__c = 'key3', name = 'name3',Mailing_ID__c= 'mid3',Type = 'Content / White Paper');  
        List<Campaign> campaigns = new List<Campaign>();
        campaigns.add(cm1);
        campaigns.add(cm2);
        campaigns.add(cm3);

        List<CampaignMember> mems = new List<CampaignMember>();
        List<Mailings__C> allMailings =  new List<Mailings__C>();
        Integer currList = 0;
        List<List<CampaignMember>> setOfMembers = new List<List<CampaignMember>>();
        setOfMembers.add(new List<CampaignMember>());
      List<CampaignMember> allMembers = new List<CampaignMember>();

      for(Campaign cmp : campaigns){ //Extract campaign keys from added campaigns so they can be matched with campaign ids
        Campaign res = [SELECT Id  FROM Campaign where Campaign_Key__c =: cmp.Campaign_Key__c];
        cmp.Id = res.Id; 
      }

        List<List<CampaignObj>> nullCampaigns = new  List<List<CampaignObj>>(); //for testing
        nullCampaigns.add(new List<CampaignObj>());
            for(Campaign cmp : campaigns){
                //match campaigns and members based on mailing id  for each iterations
                List<Mailings__C> members = [select Available_Mailing__C, Contact__C from Mailings__C  where Available_Mailing__C =: cmp.Mailing_ID__c];
                for(Mailings__C mc : members){
                    CampaignMember mem = new CampaignMember(CampaignId = cmp.id, ContactId= mc.Contact__C );
                    nullCampaigns[currList].add(camp2);
                    allMembers.add(mem);
                }
            }
         List<CampaignMember> returnList = new List<CampaignMember>();
       try{
                Database.UpsertResult[] results = Database.upsert(allMembers);
                for(Integer index = 0, size = results.size(); index < size; index++) {
                    if(results[index].isSuccess()) {
                        if(results[index].isCreated()) {
                            System.debug(allMembers[index].Id +' was created');
                        } else {
                            System.debug(allMembers[index].Id +' was updated');
                        }
                    }
                }                                   
                }
                catch (DmlException e) {
                    System.debug(e.getMessage());
                }


    } 

Best Answer

Apex sObjects do not have Ids until they are inserted. Once you perform the insert DML operation, the Id field is populated by Salesforce in your variables. Any object that is inserted as part of an upsert operation is treated in the same way.

However, objects that come back from a [SELECT fields FROM Object] SOQL query have their Ids populated because they already exist in the database (they were inserted at some time in the past). You can make changes to those objects and then update or upsert them to persist your changes.

Since Campaign Member does not allow duplicate records (those with the same pair of Contact or Lead ID and Campaign ID), it is common to query for existing Campaign Members that "match" those you're about to insert and perform an update on those existing records rather than attempting to insert new ones that fail. You can then insert new records that don't have an existing match.

You can use upsert to perform the update and insert in a single step by passing a List<CampaignMember> where some of the CampaignMember records already have their existing Id or External Id fields populated. Those records will be updated and all others inserted by the upsert operation. When the upsert completes, every record in the list will have an Id.

There is no requirement to perform an upsert. You can simply query for existing records and update them, and then insert any new records that did not already exist. Alternately, since you are not updating the Campaign Member Status or any other fields, you can simply do Database.insert(campaignMemberList, false) to allow records to succeed or fail individually. (I don't recommend this approach because there are many reasons an insert could fail besides duplication, but it is a possibility).

This:

            catch (DmlException e) {
                System.debug(e.getMessage());
            }

is a very bad idea. Swallowing exceptions always makes debugging more difficult. They are exceptions for a reason: something exceptional happened, and needs to be handled correctly by your code.

Related Topic