Sunday, August 6, 2017

MIXED_DML_OPERATION:


https://feedbackform1-dev-ed.my.salesforce.com/setup/forcecomHomepage.apexp?setupid=ForceCom

“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: Account”
You can easily run into this error if you are trying to perform DML on setup and non-setup objects in the same transaction. Non-Setup objects can be any one of standard objects like Account or any custom object, here are few examples of the Setup Objects
  • Group1
  • GroupMember
  • QueueSObject
  • User2
  • UserRole
  • UserTerritory
  • Territory
This error typically comes in two different scenarios i.e.

  1. Non-Test code
  2. Test Code
We will cover both these scenarios in detail below. But for sake of example lets take an example scenario:
  1. AFTER INSERT Trigger on Account
  2. This trigger create a GROUP from all newly created accounts.
  3. The same trigger also adds current user as member to those newly created groups.

1. Non-Test Code

Non-Test code means any Apex code that is not written for test cases, for example triggers.
Typically trigger code for the same would be something like this
trigger Account on Account (after insert) {
    //AccountHandler.afterInsert(Trigger.newMap);
    
  List<Group> newGroups = new List<Group>();
  for (Account acc : Trigger.new) {
     newGroups.add(new Group(name=acc.Name, type='Regular', DoesIncludeBosses=false));
  }
  insert newGroups;
  
  List<GroupMember> newGroupMembers = new List<GroupMember>();
  for (Group grp : newGroups) {
      newGroupMembers.add(new GroupMember(GroupId = grp.Id, UserOrGroupId=UserInfo.getUserId()));
  }
    insert newGroupMembers;
}
On creating an Account, this trigger will fail for this error:
“Apex trigger abhinav.Account caused an unexpected exception, contact your administrator: abhinav.Account: 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: Account: []: Trigger.abhinav.Account: line 14, column 1”
mixed_dml_operation error on salesforce ui, after trigger failure
The solution is simple you split the DML into future and non future context i.e.
  1. Perform DML on Non-Setup object type
  2. Perform DML on Setup object type in @future methods.
Vice versa will also work. Here is the fixed code, we create new Apex class for the trigger code

Account.trigger

trigger Account on Account (after insert) {      
  List<group> newGroups = new List<group>();
  for (Account acc : Trigger.new) {
     newGroups.add(new Group(name=acc.Name, type='Regular', DoesIncludeBosses=false));
  }
  insert newGroups;
  
  Set<id> groupIds = new Map<id  , Group> (newGroups).keySet();
  // call in future context to avoid MIXED DML conflicts
  AccountHandler.createGroupMembers(groupIds); 
}

AccountHandler.cls

This is the class with the future method to do setup and non-setup DML in different context
public class AccountHandler {
  @future
  public static void createGroupMembers(Set<Id> groupIds) {
    List<GroupMember> newGroupMembers = new List<GroupMember>();
    
    for (Id grpId : groupIds) {
        newGroupMembers.add(new GroupMember(GroupId=grpId, UserOrGroupId=UserInfo.getUserId()));
    }
      insert newGroupMembers;
  }
}
For more details on this, please check salesforce docs here.

2. Test Code

Take an example scenario, where you are having a trigger created in above fashion i.e. Setup and Non Setup objects are updated in different transactions via @future methods. But in Test case we tend to serialize all async operations like future calls via Test.startTest() and Test.stopTest(), so we are again on same condition that within same context. Here is the test code that will start failing again for the same error.
public static testmethod void test() {
 // use this to make future methods execute synchronously 
    Test.startTest();
      insert new Account(Name = 'Some Account Name');
    Test.stopTest();
}   
Here is the error that comes on executing this test
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: Account: []   
mixed_dml_operation error on test case execution

Now, how to fix this ?

As this problem is specific to test code only, this can be fixed by starting new context in the future method or the conflicting DML point. Here is the fixed future method that works well from both UI and Test code
public class AccountHandler {
  @future
  public static void createGroupMembers(Set<Id> groupIds) {
    List<GroupMember> newGroupMembers = new List<GroupMember>();
    
    for (Id grpId : groupIds) {
        newGroupMembers.add(new GroupMember(GroupId=grpId, UserOrGroupId=UserInfo.getUserId()));
    }
       
   if (Test.isRunningTest()) {
     // start new context via system.runAs() for the same user for test code only
      System.runAs(new User(Id = Userinfo.getUserId())) {
       insert newGroupMembers;
      }
    } else {
      // in non-test code insert normally
      insert newGroupMembers;
    }
  
  }
}
But this is not clean, imagine a big complex force.com development project, that is having so many future methods and triggers. Doing this patch everywhere will not look neat. So, I tried coming up with some utility class that will handle this patching at one place only, this class is named “MixedDMLOps.cls” and here is the source for the same:

MixedDMLOps.cls

/**
Handles mixed dml situations in code. It runs the DML operation in different context for Test code only, so that conflict between DML on setup and non-setup object is gone.

PLEASE NOTE:
============
methods are not named as delete, insert because they are reserved words by Apex
*/
public without sharing class MixedDMLOps {
  // DML UPDATE operation 
  public static Database.SaveResult[] up (Sobject[] objs) {
    Database.Saveresult[] updateRes;
     if (Test.isRunningTest()) {
          System.runAs(new User(Id = Userinfo.getUserId())) {
          updateRes = database.update(objs);
          }
        } else {
        updateRes = database.update(objs);
        }  
    return updateRes;
  }
  
  // DML DELETE
  public static Database.DeleteResult[] del (Sobject[] objs) {
    Database.DeleteResult[] delRes;
     if (Test.isRunningTest()) {
          System.runAs(new User(Id = Userinfo.getUserId())) {
          delRes = database.delete(objs);
          }
        } else {
        delRes = database.delete(objs);
        }  
    return delRes;
  }
  
  
  // DML INSERT
  public static Database.Saveresult[] ins (Sobject[] objs) {
    Database.Saveresult[] res;
     if (Test.isRunningTest()) {
          System.runAs(new User(Id = Userinfo.getUserId())) {
          res = database.insert(objs);
          }
        } else {
        res = database.insert(objs);
        }  
    return res;
  }
  
  
}

The future method code can be simplified a lot now, here is the one that uses MixedDMLOps
public class AccountHandler {
  @future
  public static void createGroupMembers(Set<Id> groupIds) {
    List<GroupMember> newGroupMembers = new List<GroupMember>();
    
    for (Id grpId : groupIds) {
        newGroupMembers.add(new GroupMember(GroupId=grpId, UserOrGroupId=UserInfo.getUserId()));
    }
    // the change   
    MixedDMLOps.ins(newGroupMembers);
  }
}

Friday, July 28, 2017

What is Synchronous and Asynchronous in Salesforce.



Apex can be executed synchronously or asynchronously.

Synchronous:


In a Synchronous call, the thread will wait until it completes its tasks before proceeding to next. In a Synchronous call, the code runs in single thread.

Example:

Trigger
Controller Extension
Custom Controller


Asynchronous:

An asynchronous process can execute a task "in the background" without the user having to wait for the task to finish. Force.com features such as asynchronous Apex, Bulk API, and Reports and Dashboards use asynchronous processing to efficiently process requests.

In a Asynchronous call, the thread will not wait until it completes its tasks before proceeding to next. Instead it proceeds to next leaving it run in separate thread. In a Asynchronous call, the code runs in multiple threads which helps to do many tasks as background jobs.

Example:

Batch
@future Annotation

Tuesday, July 25, 2017

Call webservice class from batch apex:

global class IBPortal_Manual_MT4Linker{

  WebService  static void Link_IBPortalMT4(Id ids) {
  Account ac;
   ac = [select IBPortalID__c,SF_LeadID__c  from Account where id=:ids];
   for (ADSS_Platform_Account__c ld: [select id, Name, Account__c,IB_ID__c,Instance__c from ADSS_Platform_Account__c where Account__c=:ids])
    {

       System.debug('--> IBPortal Manual MT4Linker');
         IBPortal_LinkMT4Account.Link_MT4AccountPort stub= new  IBPortal_LinkMT4Account.Link_MT4AccountPort();
        String output = stub.Link_Account(String.valueof(ac.IBPortalID__c),ld.Name,ac.id,ld.Instance__c,ld.IB_ID__c);
               System.debug('--> AccountUpdater'+output);
        }
  }
  }
  global class linkltoMT4batch implements Database.Batchable<sObject>,Database.AllowsCallouts{
    global set<id> accids;    
    global linkltoMT4batch(){
               // Batch Constructor
    }
    // Start Method
    global Database.QueryLocator start(Database.BatchableContext BC){
     return Database.getQueryLocator([select id from Account]);
    }
  // Execute Logic
   global void execute(Database.BatchableContext BC, List<sObject>scope){
          // Logic to be Executed batch wise   
          List<Account> acclist = (List<Account>)scope;
          for(account a :acclist){
              IBPortal_Manual_MT4Linker.Link_IBPortalMT4(a.id);
          }
   }
   global void finish(Database.BatchableContext BC){
        // Logic to be Executed at finish
   }
}  

Invoke an apex class from Batch class

 global void execute(Database.BatchableContext BC, List<sObject> scope)
{
    // if public/global and not static
    YourOtherClass instance = new YourOtherClass();
    instance.myMethod((List <Case>)scope);

    // if static
    // YourOtherClass.myMethod((List <Case>)scope);
}
And your other class' method should look like this:
 public static void(List <Case> cases)
{
    // do something with the cases
}
-----------
Batch Apex cannot be invoked from a @future method.
You can run a frequently scheduled Apex Class (say every 5-10 mins) that scans a table to check whether it should run.
When your data import finishes, you can set this data, which then indicates to the Batch Job (the next time it runs) that the data is available and it executes.
If the flag is not set, the start method does not populate a QueryLocator, and therefore no processing would occur in the Execute method.
The other alternative is to have a webservice method which can invoke batch apex. This will however need to be externally invoked and is probably more relevant to a data push rather than pull.