Handling “MIXED_DML_OPERATION” with future calls

 You can run “MIXED_DML_OPERATION” error when you are trying to perform DML on setup and non-setup objects in the same transaction.This restriction exists because some sObjects affect the user’s access to records in the org.Non-Setup objects are standard objects like Account or any custom object.  here is the few Setup Object
  • ObjectPermissions
  • PermissionSet
  • PermissionSetAssignment
  • QueueSObject
  • ObjectTerritory2AssignmentRule
  • ObjectTerritory2AssignmentRuleItem
  • RuleTerritory2Association
  • SetupEntityAccess
  • Territory2
  • Territory2Model
  • UserTerritory2Association
  • User
  • GroupMember
  • FieldPermissions

Use case: – Once “Customer Satisfaction Survey” is completed we need to assign it to the  “Internal_Employee” group. Here is the simple trigger


 trigger ServeyTrigger on Customer_Satisfaction_Survey__c (after insert) {
    Group g=[select Id from Group Where DeveloperName='Internal_Employee'];

    List<GroupMember>listGroupMember =new List<GroupMember>();
    for (Customer_Satisfaction_Survey__c cs  : Trigger.new){
        GroupMember gm= new GroupMember();
        gm.GroupId=g.id;
        gm.UserOrGroupId = UserInfo.getUserId();
        listGroupMember.add(gm);
    }
    insert listGroupMember;

}

 

Once you try to insert the new records you will get the below error message

Review all error messages below to correct your data.
Apex trigger ServeyTrigger caused an unexpected exception, contact your administrator: ServeyTrigger: execution of AfterInsert caused by: System.DmlException: Insert failed. First exception on row 0; first error: MIXED_DML_OPERATION, DML operation on setup object is not permitted after you have updated a non-setup object (or vice versa): GroupMember, original object: Customer_Satisfaction_Survey__c: []: Trigger.ServeyTrigger: line 11, column 1

To solve this problem simply you can add the Second DML into future call

  1. Create a method that performs a DML operation on one type of sObject.
  2. Create a second method that uses the future annotation to manipulate a second sObject type.

Now update the trigger as shown below

trigger ServeyTrigger on Customer_Satisfaction_Survey__c (after insert) {
  ServeyAsyncCall.assignToUser(trigger.newMap.keySet());
}

Apex Class:

 

 public class ServeyAsyncCall {
    @future
    public static void assignToUser(Set<Id> setofIds){
        Group g=[select Id from Group Where DeveloperName='Internal_Employee'];
        List<Customer_Satisfaction_Survey__c> records =[Select Id , Name from Customer_Satisfaction_Survey__c where id in:setofIds];
        List<GroupMember>listGroupMember =new List<GroupMember>();
        for (Customer_Satisfaction_Survey__c cs  :records){
            GroupMember gm= new GroupMember();
            gm.GroupId=g.id;
            gm.UserOrGroupId = UserInfo.getUserId();
            listGroupMember.add(gm);
        }
        if (!Test.isRunningTest()) {
            insert listGroupMember;
        }
    }
}

Apex Test Class:- 

@isTest
private class ServeyAsyncCall_test {

    private static testMethod  void bypassMixedDML(){
        // You can insert the user with @future  for this test method . But in this test class simply i queried
        // InsertUser.callInsertfuture()
        User thisUser = [SELECT Id FROM User WHERE Id = :UserInfo.getUserId()];
        System.runAs (thisUser) {

            Customer_Satisfaction_Survey__c cs = new Customer_Satisfaction_Survey__c();
            cs.Comments__c='Hello' ;
            cs.Name='Test Class';
            insert cs ; 

        }
    }
}