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>();

        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 );
         List<CampaignMember> returnList = new List<CampaignMember>();
                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) {


1 Answer 1


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


            catch (DmlException e) {

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.

  • So you're saying that the new Campaign Member objects should first be inserted and then a query should be run to find those records again? After they're inserted, they should then have an id right? Mar 7, 2018 at 16:22
  • Ok, so it seems that in this case, the insert should just be used instead of the upsert for the method. So then there are just some sObjects that have auto generated id field values upon upserts and some that don't? Mar 7, 2018 at 16:32
  • I'm not sure if I quite understand what you're saying. You were saying that the id field on Campaign Member won't be populated until it has been inserted first so it doesn't seem possible to do an upsert and an insert in the same operation since each record would have to preexist (via the insert) to have the id before an upsert could be done. I've changed the code to do an insert but I'm getting a duplicate record error which is something that I would think would be handled via upsert. This code isn't really being used for updates as much as just to create new members for campaigns. Mar 7, 2018 at 17:05
  • Using Database.insert() is correctly inserting the records and seems to be the solution that makes the most sense since no updates are being done in the method. I'm trying to store the failed inserts in a list but I'll have to find the best way to do that. Mar 7, 2018 at 20:08

You must log in to answer this question.

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