2

I have a Contact trigger 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 associated 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;
    }
}

2 Answers 2

1

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.

2
  • 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 Commented Nov 16, 2018 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
    Commented Nov 16, 2018 at 15:42
0

A subset of User fields are "special" and fall under mixed DML restriction. From the doc:

You can update a User in a transaction with other sObjects in Apex code saved using Salesforce API version 15.0 and later if the user isn’t included in an Lightning Sync configuration (either active or inactive) and the following fields are not updated:

UserRoleId
IsActive
ForecastEnabled
IsPortalEnabled
Username
ProfileId

These fields must be updated in a separate transaction based on your preferred async Apex implementation vehicle.

Other User fields can be updated in the same transaction as long as you adhere to the VF exclusion:

If you're using a Visualforce page with a custom controller, you can't mix sObject types with any of these special sObjects within a single request or action.

You must log in to answer this question.

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