Loading

SOQL queries and criteria-based sharing considerations

Date de publication: Oct 13, 2022
Description
If a "with sharing" Apex class performs a SOQL query in the context of a transaction where a criteria based sharing rule should share some rows with the running user, the query will not be able to retrieve these rows as the shares that provide access to said rows are written to the database during the commit phase (right after the Apex transaction finishes).
Résolution
As per the Order of Execution, criteria based sharing rules are evaluated at the end of the execution flow, and shares created in memory at this point are saved during the commit phase. "Commit" for Salesforce requests happens just before the response has been sent to the user, and after all Apex execution for the request has finished.

As a result a SOQL query won't return rows that are shared via a criteria based sharing rule evaluated in the same transaction, as the Apex transaction doesn't have access to committed data.

Workarounds:
  • Consider changing the Apex controller's modifier to "without sharing"
  • Invoke the required SOQL query in a different transaction to the transaction where the criteria based sharing rule is evaluated.

Sample scenario:

This can be reproduced by following these steps:

a) Create the following Apex controller, Visualforce page, and Apex trigger.
public with sharing class AccountQueryIssue {
    public String debugString { get; set; }
    private Account a, b;    
    
    public AccountQueryIssue() {
        debugString = '';
    }
    
    public void createAccount() {
        // Replace record type ID with ID of an account record type
        a = new Account(Name = 'Acme '+ System.now(), recordTypeId='012410000002S6O');
        insert a;
        debugString += '<br/>Inserted account:<br/>' + JSON.serialize(a) + '<br/>';

        try {
            b = [SELECT Id, Name, Owner.Alias FROM Account where id = :a.Id];
        } catch(Exception e) { }
        
        if(b != null)
            debugString += '<br/>Queried account:<br/>' + JSON.serialize(b) + '<br/>';
        else
            debugString += '<br/>Could not query account<br/>';
    }
}
VF Page
<apex:page controller="AccountQueryIssue" showHeader="false" sidebar="false">
    <apex:form >
        <apex:commandButton action="{!createAccount}" value="Create Account"/>
    </apex:form>
    <apex:outputText escape="false" value="{!debugString}"/>
</apex:page>
Apex Trigger
trigger ChangeOwner on Account (before insert) {
    for(Account a : Trigger.new) {
        // Replace OwnerId with ID of user with role CEO
        a.OwnerId = '00541000000HJYT';
    }
}

b) Create a criteria based sharing rule that shares accounts where the record type is the one used to create the account via the Apex controller with users with a role below CEO.

c) Log in as a user with a role below CEO, navigate to above Visualforce page, and click Create Account. "Could not query account" will be displayed. However the running user will be able to navigate to the account by going to https://<instance>.salesforce.com/<accountId>.
Numéro d’article de la base de connaissances

000382331

 
Chargement
Salesforce Help | Article