9

Within my community there are customer users with Customer Community Plus licenses who are considered to be "company admins" for their respective companies. A company admin needs to be able to add other users from their company, which effectively involves creation of a Contact record and a User record for the new user.

The problem I'm experiencing is this:

  • If I try inserting the Contact record and the User record in the same code context, I get a MIXED_DML_OPERATION exception.
  • If I try using Site.createExternalUser(), I get this error: "You are already logged in."

Other than breaking out the Contact record creation and User record creation into two operations that execute in two separate code contexts (e.g., actions on a Visualforce page), is there any way to efficiently enable a logged in customer user to add another user?

  • 1
    You don't need to have two separate actions on your visualforce page. I have gotten around this by having the Contact record created and inserted in a method called by the method that creates and inserts the user record. There are several articles about this if you search for MIXED_DML_OPERATION exception. – Jesse Milburn Dec 23 '15 at 16:36
  • Hi @JesseMilburn, do you mean putting the User creation code inside a @future method? – Marty C. Dec 23 '15 at 19:00
  • Sorry it has been a while I lost track of this. I did not have to use the @future annotation. – Jesse Milburn Jan 11 '16 at 18:41
  • I have a controller extension with two actions: saveContact() and saveUser(). I tried two approaches based on @JesseMilburn's suggestion: 1) call saveContact() as the first operation in saveUser(), and 2) use a third action method savePortalUser() which calls the previous two methods in sequence. Same problem still exists. – Marty C. Feb 2 '16 at 18:56
  • I hope I am not confusing your question. I will answer with code that works for me as a currently logged in user, that is an admin for a community on a customer community plus license user. – Jesse Milburn Feb 2 '16 at 19:48
5
+500

Explanation

Take a look at sObjects That Cannot Be Used Together in DML Operations. It is not your call to UserInfo.getUserRoleId() that is causing the error. You get the error because you are setting the UserRoleId field to a non-null value.

Based on the below, you could in theory write a class that can insert a User with a UserRoleId if you set the API Version to 14.0 or below. I wouldn't recommend it, however, as there are certainly better workarounds.

  • Don't specify UserRoleId or specify it as null
  • Setting the UserRoleId in a trigger or @future method might work.

Example

Any code sample along these lines will pass:

insert new User(
    // required fields
    UserRoleId = null
);
insert anotherRecord;

API Version 15.0 +

Insert

You can insert a user in a transaction with other sObjects in Apex code saved using Salesforce API version 15.0 and later if UserRoleId is specified as null.

Update

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 following fields are not also updated:

  • UserRoleId
  • IsActive
  • ForecastEnabled
  • IsPortalEnabled
  • Username
  • ProfileId

Prior Versions

Insert

You can insert a user in a transaction with other sObjects in Apex code saved using Salesforce API version 14.0 and earlier.

Update

You can update a user in a transaction with other sObjects in Apex code saved using Salesforce API version 14.0 and earlier.

1

This is what worked for me Marty. There is a bit of additional logic from our business use case. But you should be able to pull that out fairly easily.

I didn't add any code for the page, but you don't seem to have any confusion about getting data back and forth from your controller and page.

public void modifyUserConfirm() {
    createUser();

        Database.DMLOptions dlo = new Database.DMLOptions();
        dlo.EmailHeader.triggerUserEmail = true;

        try {
            Database.SaveResult saveResults;
            if (isNewUser && activeUsers.size() >= maximumNumberOfUsers) {
                throw new DmlException('You have exceeded the amount of active users your organization is allotted.');
            } else {
                saveResults = Database.update(user, dlo);
            }

            for (Database.Error result : saveResults.getErrors()) {
                throw new DmlException(result.getMessage());
            }

            if (isNewUser) activeUsers.add(user);
        } catch (DmlException e) {
            System.debug(e);
            ApexPages.addMessage(new ApexPages.Message(ApexPages.Severity.ERROR, e.getMessage()));
            if (isNewUser && !Test.isRunningTest()) delete contact;//remove contact record if user is not created.
        } catch (Exception e) {
            System.debug(e);
        }
}
private Contact createContactForUser() {
        contact = new Contact(
            AccountId = userAccountId,
            FirstName = user.FirstName,
            LastName = user.LastName,
            Email = user.Email,
            RecordTypeId = aRecordType
        );

        insert contact;
        return contact;
    }

private void createUser() {
    if (isNewUser) {
        contact = createContactForUser();
        User newUser = new User(
            FirstName = user.FirstName,
            LastName = user.LastName,
            UserName = user.UserName,
            Email = user.Email,
            Phone = user.Phone,
            Title = user.Title,
            ProfileId = selectedProfileId,
            ContactId = contact.Id
        );
        user = newUser;
    }

    user.LanguageLocaleKey = 'en_US';
    user.LocaleSidKey = 'en_US';
    user.TimeZoneSidKey = 'America/Chicago';
    user.EmailEncodingKey = 'UTF-8';
    user.Phone = FormatPhone(user.Phone);
    user.Alias = user.FirstName.substring(0, 1) + user.LastName.substring(0, 1);
        user.CommunityNickname = user.FirstName.substring(0, 1) + user.LastName;
    }
1

The root cause of my MIXED_DML_OPERATION exception turned out, unexpectedly, to be a call to UserInfo.getUserRoleId(). Removing this line of code from my extension resolved the problem, and I'm now able to create a Contact record and a User record in the same method invocation, regardless of whether the DML operations are in the same method or in different methods.

For what it's worth, the following Visualforce page and controller (tested in Spring '16) demonstrate the ability to create a new community user when logged in as another community user.

ExtrDev419.page

<apex:page id="page"
           controller="ExtrDev419Controller">
    <apex:pageMessages/>
    <apex:form id="form">
        <apex:pageBlock>
            <apex:pageBlockButtons>
                <apex:commandButton action="{!savePortalUser}" value="Save"/>
            </apex:pageBlockButtons>
            <apex:pageBlockSection title="{!$ObjectType.Account.name}">
                <apex:pageBlockSectionItem>
                    <apex:outputLabel value="{!$ObjectType.Contact.fields.FirstName.label}"></apex:outputLabel>
                    <apex:inputField value="{!contact.FirstName}"/>
                </apex:pageBlockSectionItem>
                <apex:pageBlockSectionItem>
                    <apex:outputLabel value="{!$ObjectType.Contact.fields.LastName.label}"></apex:outputLabel>
                    <apex:inputField value="{!contact.LastName}"/>
                </apex:pageBlockSectionItem>
                <apex:pageBlockSectionItem>
                    <apex:outputLabel value="{!$ObjectType.Contact.fields.Email.label}"></apex:outputLabel>
                    <apex:inputField value="{!contact.Email}"/>
                </apex:pageBlockSectionItem>
                <apex:pageBlockSectionItem>
                    <apex:outputLabel value="{!$ObjectType.Contact.fields.MailingCountry.label}"></apex:outputLabel>
                    <apex:inputField value="{!contact.MailingCountry}"/>
                </apex:pageBlockSectionItem>
                <apex:pageBlockSectionItem>
                    <apex:outputLabel value="{!$ObjectType.Contact.fields.MailingState.label}"></apex:outputLabel>
                    <apex:inputField value="{!contact.MailingState}"/>
                </apex:pageBlockSectionItem>
            </apex:pageBlockSection>
            <apex:pageBlockSection title="{!$ObjectType.Account.name}">
                <apex:pageBlockSectionItem>
                    <apex:outputLabel value="{!$ObjectType.Account.fields.Name.label}"></apex:outputLabel>
                    <apex:inputField value="{!account.Name}"/>
                </apex:pageBlockSectionItem>
                <apex:pageBlockSectionItem>
                    <apex:outputLabel value="{!$ObjectType.Account.fields.BillingCountry.label}"></apex:outputLabel>
                    <apex:inputField value="{!account.BillingCountry}"/>
                </apex:pageBlockSectionItem>
                <apex:pageBlockSectionItem>
                    <apex:outputLabel value="{!$ObjectType.Account.fields.BillingState.label}"></apex:outputLabel>
                    <apex:inputField value="{!account.BillingState}"/>
                </apex:pageBlockSectionItem>
            </apex:pageBlockSection>
        </apex:pageBlock>
    </apex:form>
</apex:page>

ExtrDev419Controller.cls

public without sharing class ExtrDev419Controller {

    private Account newAccount;
    private Contact newContact;
    private User newUser;

    public ExtrDev419Controller() {

        // Initialize the account
        newAccount = new Account(
            BillingCountry = 'United States',
            BillingState = 'South Carolina',
            Name = 'Carolina Panthers');

        // Initialize the contact
        newContact = new Contact(
            MailingCountry = 'United States',
            MailingState = 'South Carolina',
            Email = '@gmail.com');

        // Initialize the user
        newUser = new User();
    }

    public Account getAccount() {
        return newAccount;
    }

    public Contact getContact() {
        return newContact;
    }

    public PageReference savePortalUser() {
        PageReference nextPage = null;

        try {
            insert newAccount;

            newContact.AccountId = newAccount.Id;
            insert newContact;

            List<Profile> communityProfiles = [
                SELECT Id
                FROM Profile
                WHERE Name = 'Customer Community Plus Login User'
            ];

            newUser.ContactId = newContact.Id;
            newUser.FirstName = newContact.FirstName;
            newUser.LastName = newContact.LastName;
            newUser.Email = newContact.Email;
            newUser.Username = newContact.Email;
            newUser.Alias = newContact.LastName;
            newUser.CommunityNickname = newContact.LastName + '.' + DateTime.now().getTime();
            newUser.TimeZoneSidKey = UserInfo.getTimeZone().getID();
            newUser.LocaleSidKey = UserInfo.getLocale();
            newUser.EmailEncodingKey = 'UTF-8';
            newUser.ProfileId = communityProfiles.get(0).Id;
            newUser.LanguageLocaleKey = UserInfo.getLanguage();
            insert newUser;
        }
        catch (System.Exception e) {
            ApexPages.addMessages(e);
        }

        return nextPage;
    }

    public void setAccount(Account value) {
        newAccount = value;
    }

    public void setContact(Contact value) {
        newContact = value;
    }
}

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.