function readOnly(count){ }
Starting November 20, the site will be set to read-only. On December 4, 2023,
forum discussions will move to the Trailblazer Community.
+ Start a Discussion
Nathan CrosbyNathan Crosby 

Need help resolving governor limits

I have a trigger that calls a class which establishes record sharing for an object based on the record owner and their role. Essentially I am sharing the record with the owner's role. The org has entirely too many roles that will quickly exceed the number of predefined sharing rules that can be established so this trigger/class solution is necessary across a number of private objects to ensure members of the same role share the same access to records anyone in the same role owns.

My challenge comes in establishing the data set needed to create the sharing in the custom object's sharing object (in this case Team__Share). Specifically, because I am sharing with a Role I need to first grab the record owner, determine their role, and then query the Group object to get the Group Id to pass back into the Team__Share object to set the UserOrGroupId (using the Role Id to set UserOrGroupId causes an error as it is looking for either a User Id or Group Id).

Here is my code for both the trigger and the class with the SOQL that is generating the governor limit execution error commented. This works like a charm if it is just one record being updated/inserted, but obviously fails once testing with larger sets of data.
Code:
trigger TeamSharingTrigger on Team__c (after insert, after update) {
 
 List<ID> ownerId = new List<ID>();
 
 for (Team__c teamNew1: Trigger.New){
  ownerId.add(teamNew1.OwnerId);
 }
 
 Map<ID,User> u = new Map<ID,User>([Select Id, UserRoleId From User where Id in :ownerId]);
 
 ID roleId;
 for (Team__c teamNew2: Trigger.New){
  roleId = u.get(teamNew2.OwnerId).UserRoleId;
  if(roleid != null){
   TeamSharingClass.manualShareAll(teamNew2.Id,roleId);
  }
 }
 
}





public class TeamSharingClass {
 
 public static boolean manualShareAll(Id recordId, Id userOrGroupId){

   /*Since this is a Role and not a User that I am trying to share with I 
     need to grab the Id of the Group for that role.
     This is the SOQL that causes the governor limits (executed for each call)*/
      ID groupID = [Select g.Id From Group g where g.RelatedId=:userOrGroupId and g.Type='Role'].Id;
      
      Team__Share teamShr = new Team__Share();
   teamShr.ParentId = recordId;
   teamShr.UserOrGroupId = groupID;
   teamShr.AccessLevel = 'Edit';
   teamShr.RowCause = Schema.Team__Share.RowCause.Manual;
   Database.SaveResult sr = Database.insert(teamShr,false);
         
      // Process the save results.
      if(sr.isSuccess()){
         // Indicates success
         return true;
      }
      else {
         // Get first save result error.
         Database.Error err = sr.getErrors()[0];
         
         // These sharing records are not required and thus an insert exception is acceptable. 
         if(err.getStatusCode() == StatusCode.FIELD_INTEGRITY_EXCEPTION  &&  
                  err.getMessage().contains('AccessLevel')){
            // Indicates success.
            return true;
         }
         else{
            // Indicates failure.
            return false;
         }
       }
   }
   
}

 



Shadow8002Shadow8002
The reason that you are getting the governor limit error is because of the fact that the query is inside the mathod that is being called from inside your 'for' loop. You will have to move the query outside of the method in order to handle larger sets of data.

My suggestion would be to use maps.

> You are anyway capturing all the Role Id's in a map.
> Create another method that constructs a second map for <User Role Id, Group Id> by passing the KeySet of the map
> Make sure that you call the new method first and put it outside of the for loop. This method will only execute once.
> Once your map is constructed, then use it to get the appropriate groupId

Hope this helps

Nathan CrosbyNathan Crosby
I'm close....I understand why I'm hitting the governor limit and even agree that it's a bad design. Where I'm having a hard time is pulling together a solution (the code) such as the one you describe. I'm a bit new to Maps, Lists, and Sets and can't figure out how to iterate through the Map and populate the Map in a way that gives me what I'm looking for.....any potential starting points with sample code would be greatly appreciated.
Nathan CrosbyNathan Crosby
Thanks Shadow8002. You helped to point me in the right direction and after a few attempts with Sets, Lists, and Maps I was able to get this to run like a charm for both the single record transaction and bulk transactions.

For anyone else that's interested, below is the code that was used. The Team object is the custom object that's at play and could be replaced with any other object and the below code should still work just fine.

This trigger creates a manual sharing rule for the current owner and shares the record with everyone else in the owner's role (I beefed up the comments too).


Code:
trigger TeamSharingTrigger on Team__c (after insert, after update) {
 
 /* --- WHO ARE THE RECORD OWNERS --- */
 //LIST OF OWNERIDS
 List<ID> ownerId = new List<ID>();
 //POPULATE LIST OF OWNERIDS FROM TRIGGER.NEW 
 for (Team__c teamNew1: Trigger.New){
  ownerId.add(teamNew1.OwnerId);
 }
 
 /* --- FOR EACH RECORD OWNER RETRIEVE THEIR ROLE ID --- */
 //MAP OF USER OBJECTS TO USE FOR MAPPING BACK TO
 //THE OWNERS ROLEID USING THE ABOVE ownerId LIST
 Map<ID,User> u = new Map<ID,User>([Select Id, UserRoleId From User where Id in :ownerId]);
 //COLLECTION OF ROLE IDS
 List<ID> roleId = new List<ID>();
 //POPULATE roleId COLLECTION BY ITERATING THROUGH EACH ownerId LIST ITEM
 //AND RETRIEVING THE ROLEID FROM THE MAP OF USER OBJECTS u
 for (ID o: ownerId){
  roleId.add(u.get(o).UserRoleId);
 }
 
 /* --- FOR EACH ROLEID RETRIEVE ITS GROUP ID FROM THE GROUP OBJECT --- */
 //MAP OF GROUP OBJECTS TO USE FOR MAPPING BACK TO
 //THE GROUP ID (key) OF EACH ROLE ID (value)
 Map<ID,Group> g = new Map<ID,Group>([Select Id, RelatedId from Group where Type='Role' and RelatedId in :roleId]);
 
 /* --- REVERSE THE KEY-VALUE SO THE KEY IS THE ROLEID AND THE VALUE IS THE GROUPID --- */
 //UNIQUE SET OF GROUP IDs POPULATED WITH THE KEYSET FROM THE MAP OF GROUPS
 //TO USE FOR ITERATIBG THROUGH AND POPULATING A NEW MAP WITH ROLEID (key) AND GROUPID (value)
 //TO REVERSE THE KEY-VALUE PAIR
 Set<ID> groupId = new Set<ID>();
 groupId = g.keySet();
 //NEW MAP OF IDs USED FOR REVERSING THE KEY-VALUE
 Map<ID,ID> roleGroupIds = new Map<ID,ID>();
 //ITERATE THROUGH EACH ID IN THE groupId SET AND POPULATE THE roleGroupIds MAP
 for (ID grpId: groupId){
  roleGroupIds.put(g.get(grpId).RelatedId,grpId); 
 }
 
 /* --- PREPARE A MAP COLLECTION OF IDs TO SEND TO THE APEX CLASS TO PROCESS THE SHARING RULES --- */
 //VARIABLES FOR HOLDING IDs TO POPULATE INTO THE recordGroupIds MAP
 ID rId;
 ID gId;
 Map<ID,ID> recordGroupIds = new Map<ID,ID>();
 //ITERATE THROUGH THE TRIGGER.NEW COLLECTION AND POPULATE THE recordGroupIds MAP
 for (Team__c teamNew2: Trigger.New){
  //BASED ON THE OWNERID GET THE CORRECT ROLEID
  rId = u.get(teamNew2.OwnerId).UserRoleId;
  //USE THE ROLEID TO RETRIEVE THE GROUPID
  gId = roleGroupIds.get(rId);
  if(rId != null){
   //POPULATE THE RECORDID AND GROUPID
   recordGroupIds.put(teamNew2.Id,gId);
  }
 }
 
 /* --- CALL THE APEX CLASS TO PROCESS THE SHARING RULES --- */
 TeamSharingClass.manualShareAll(recordGroupIds);
 
}

 and the APEX class....

Code:
public class TeamSharingClass {
 
 public static boolean manualShareAll(Map<ID,ID> RecordGroup){
      
      //RETURN VALUE VARIABLE
      Boolean saveResult;
      
      //UNIQUE SET OF RECORDIDs FROM THE PASSED IN MAP
      Set<ID> rId = new Set<ID>();
      rId = RecordGroup.keySet();
      
      //LIST OF TEAM__SHARE OBJECTS 
      //USED TO POPULATE A COLLECTION OF TEAM__SHARE OBJECTS TO INSERT
      List<Team__Share> teamShrs = new List<Team__Share>();
   
   //FOR EACH ID IN THE ABOVE SET CREATE A NEW TEAM__SHARE OBJECT
   //AND ADD IT TO THE teamShrs LIST CREATED ABOVE
   for (ID recId: rId){
    Team__Share teamShr = new Team__Share();
    teamShr.ParentId = recId;
    teamShr.UserOrGroupId = RecordGroup.get(recId);
    teamShr.AccessLevel = 'Edit';
    teamShr.RowCause = Schema.Team__Share.RowCause.Manual;
    teamShrs.add(teamShr);
   }
   
   //INSERT THE teamShrs COLLECTION CREATING THE SHARING RULES
   Database.SaveResult[] listSR = Database.insert(teamShrs,false);
      
      // Process the save results.
   for(Database.SaveResult sr: listSR){
       if(sr.isSuccess()){
          // Indicates success
          saveResult = true;
       }
       else {
          // Get first save result error.
          Database.Error err = sr.getErrors()[0];
          
          // These sharing records are not required and thus an insert exception is acceptable. 
          if(err.getStatusCode() == StatusCode.FIELD_INTEGRITY_EXCEPTION  &&  
                   err.getMessage().contains('AccessLevel')){
             // Indicates success.
             saveResult = true;
          }
          else{
             // Indicates failure.
             saveResult = false;
          }
        }
    }
    
    return saveResult;
   }
   
}