0

I have a Contact Trigger Handler that, after update, checks to see if some fields (Email or Team) were changed, and if they were, it updates the fields on the Contact's Community User record. These two different updates are nearly identical in implementation, and it is impossible for a single record to ever meet both conditions, so a record is only being added to ONE of TWO final lists for update.

If I update the Contacts team and save the record, the users team is updated without an issue, but if I update the Contacts email it fails with a MIXED_DML_OPERATION saying I can't update User after updating Contact. There are no triggers running on the User object that update the contact after the User record is updated.

System.DmlException: Update failed. First exception on row 0 with id 0051h000001YZ59AAG; first error: MIXED_DML_OPERATION, DML operation on setup object is not permitted after you have updated a non-setup object (or vice versa): User, original object: Contact: []


Question: Why is one of these transactions being treated differently than the other? Both are triggered by an update to the Contact record, both find Contacts that meet appropriate conditions and put them in a list, a Contact can never be in both lists in the same transaction, and one of the User updates works fine while the other does not.


Code:

ContactTriggerHandler.cls:

if (CONDITION1) {
    if (!isCommunityUser) {
            contactsForCommunity.add(c);
    } else {
        if (teamChanged) {
            teamChanges.add(c);
        }
    }
} else {
    if(!isCommunityUser){
        if (String.isBlank(relatedContact) && !String.isBlank(conEmail)) {
            contactsForCommunity.add(c);
        }
    }else{
        if(emailChanged && !emailInUse){
            updateUsernameContacts.add(c);
        }
    }
}

if(!updateUsernameContacts.isEmpty()){
    UserUtil.updateUsernames(updateUsernameContacts);
}

if(!teamChanges.isEmpty()){
    UserUtil.updateLearningTracks(teamChanges);
}

UserUtil.updateLearningTracks (Works):

public static void updateLearningTracks(List<Contact> contacts){
    System.debug('Start UserUtil.updateLearningTracks');
    String userLearningTrack,contactLearningTrack;
    Boolean isChanged;
    List<User> toUpdate = new List<User>();
    List<User> users = [SELECT Id,Username,
                            Email,ContactId,
                            Contact.Team_Assignment__c 
                        FROM User 
                        WHERE ContactId IN : contacts];

    for(User u : users){
        contactLearningTrack = u.Contact.Team_Assignment__c;
        userLearningTrack = u.Team_Assignment__c;
        isChanged = contactLearningTrack != userLearningTrack;
        if(isChanged){
            toUpdate.add(u);
        }

    }
    if(!toUpdate.isEmpty()){
        update toUpdate;
    }
    System.debug('End UserUtil.updateLearningTracks');
}

UserUtil.updateUsernames (DOES NOT WORK - MIXED DML) :

public static void updateUsernames(List<Contact> contacts){
    String username;
    String conEmail;
    Boolean isChanged;
    List<User> toUpdate = new List<User>();
    List<User> users = [SELECT Id,Username,
                            Email,ContactId,
                            Contact.Email 
                        FROM User 
                        WHERE ContactId IN : contacts];

    for(User u : users){
        username = u.Username;
        conEmail = u.Contact.Email;
        isChanged = username != conEmail;
        if(isChanged){
            u.Username = conEmail;
            u.Email = conEmail;
            toUpdate.add(u);
        }
    }
    if(!toUpdate.isEmpty()){
        update toUpdate;
    }
}
0

I suspect that you've simply failed to trigger the condition. Mixed DML isn't something that just goes away, or rather, if it is working, it's a bug, and you shouldn't depend on it. You need to make the methods @future or Queueable to avoid this error.

  • While I agree with you that ultimately it's best to isolate these methods in a Queueable context, I specifically didn't do that (yet) while designing the new implementation because I wanted to see where the boundary really was. (We had an old implementation that ran into similar issues, but it was always because the User trigger had code to update the Contact after). I'm still kind of curious about why two seemingly identical methods have different results, any additional insight? The only difference I can pinpoint is that one is updating a custom field on User and the other is standard fields – Morgan Marchese Nov 16 '18 at 13:07
  • 1
    @MorganMarchese Until I see documentation that says you're allowed to update a community user and a contact in the same transaction, I have to believe that you're probably just experiencing a bug. I'll see if I can find any docs that explain it. – sfdcfox Nov 16 '18 at 15:42

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.