5

I have created a selector class on Campaign object to be used in with sharing context like below:

public with sharing class CampaignSelector {

    public static Map<Id, Campaign> getCampaigns() {
        Map<Id, Campaign> campaignsByIds = new Map<Id, Campaign>([
                SELECT Id,
                        Name
                FROM Campaign
                WHERE Start_Date__c <= TODAY
                AND End_Date__c >= TODAY
        ]);
        return campaignsByIds;
    }
}

Now there is a situation where I have to use the same query in without sharing context. I can create a new class defined as without sharing and then add the selector query. But is there any other cleaner approach?

nica
  • 1,400
  • 3
  • 18
  • 37

2 Answers2

4

One way would be to use inherited sharing instead of specifying the class using with or without sharing.

public inherited sharing class CampaignSelector {

    public static Map<Id, Campaign> getCampaigns() {
        Map<Id, Campaign> campaignsByIds = new Map<Id, Campaign>([
                SELECT Id,
                        Name
                FROM Campaign
                WHERE Start_Date__c <= TODAY
                AND End_Date__c >= TODAY
        ]);
        return campaignsByIds;
    }
}

Now if you call the class from without sharing class it will run in system mode.

And if you call the class from the with sharing it will execute in with sharing mode.

If you have a same class that needs to run in with sharing and without sharing , create an inner class using without sharing. Call your logic from the inner class to run in system context.

Mohith Shrivastava
  • 91,131
  • 18
  • 158
  • 209
  • Well, the class from where I am calling this selector class is in 'with sharing' context. – nica May 12 '20 at 06:18
  • You will need another inner class which uses without sharing for your scenario and call from there – Mohith Shrivastava May 12 '20 at 06:21
  • 1
    This approach is harder to maintain. You will need to have inner classes in every place where you need to use without sharing context. It is better to have a single point which determines the sharing context at the selector class itself – nchursin May 12 '20 at 06:53
4

I had the same requirement once. So what we did is we had a class that executed the query to be abstract and we created instances of its successors, that determined the sharing model. This way you still only one query to maintain and a single entry point, but you're free to switch the sharing context on flight.

For your case this will look something like this:

public class CampaignSelector {

    private static CampaignDataService dataService {
        get {
            if (null == dataService) {
                // use with sharing by default
                dataService = new WithSharing();
            }
            return dataService;
        }
        set;
    }

    public static Map<Id, Campaign> getCampaigns() {
        return dataService.getCampaigns();
    }

    public static void switchSharing(Boolean useSharing) {
        // this method is used to switch sharing model
        if (useSharing) {
            dataService = new WithSharing();
        } else {
            dataService = new WithoutSharing();
        }
    }

    private abstract class CampaignDataService {
        public Map<Id, Campaign> getCampaigns() {
            Map<Id, Campaign> campaignsByIds = new Map<Id, Campaign>([
                    SELECT Id,
                            Name
                    FROM Campaign
                    WHERE Start_Date__c <= TODAY
                    AND End_Date__c >= TODAY
            ]);
            return campaignsByIds;
        }
    }

    private with sharing class WithSharing extends CampaignDataService {}
    private without sharing class WithoutSharing extends CampaignDataService {}
}

The query is still can be executed by calling CampaignSelector. getCampaigns(). By default it will be executed with sharing. To switch the sharing context you need to call CampaignSelector.switchSharing(false) - this will init dataService as an instance of WihtoutSharing and execute the query in without sharing context. You can switch back to with sharing in the same transaction by calling CampaignSelector.switchSharing(true).

The problem with static methods is that they block you from using inheritance and other cool OOP stuff

nchursin
  • 1,001
  • 8
  • 15
  • 1
    This is good approach. I think I have used this as well. – Mohith Shrivastava May 12 '20 at 07:04
  • The thing is it boils down to same principal of using inner classes to solve the issue – Mohith Shrivastava May 12 '20 at 07:19
  • Yup. It just makes it easy to maintain by reducing code duplication – nchursin May 12 '20 at 07:23
  • @snl before calling CampaignSelector.getCampaigns() call CampaignSelector. switchSharing(false). This will intialize dataService as WithoutSharing context and the query will be executed in without sharing context. Updated the answer with that info – nchursin May 12 '20 at 07:28
  • @blank Thank you! – nica May 12 '20 at 07:34
  • 3
    @snl Also, see my example on this technique as well. It may give you some other ideas of what's possible. – sfdcfox May 12 '20 at 13:17
  • The example provided in this answer will only work if you call a method in the derived class that returns getCampaigns() from the base class. Simply declaring a sharing mode on the derived classes will have no effect by itself. – geeljire Nov 24 '20 at 16:29
  • @geeljire That's what public method CampaignSelector.getCampaigns() does. It incapsulates calls to the inner derived classes. And dataService controls whether with or without sharing derived class is used. – nchursin Nov 24 '20 at 17:29
  • @nchursin, that part makes sense. What I'm saying is the declaration itself is not sufficient to switch modes if you're invoking a method in the parent class. In that case, the parent class's sharing mode will take effect because the location of the method also matters, not just the class definition. In fact, b/c the declaration is omitted on the other classes, the sharing mode that takes effect in your code is actually the mode of whatever calls CampaignSelector. In short, you have to call a method on the derived class for the declarations to work. – geeljire Nov 25 '20 at 17:48
  • @geeljire could you please provide a link to a git repo with a test to prove your point? I disagree with your reasoning, because method calls only inherit sharing rules as you mentioned when declaration is omitted. But the callstack here is caller (whatever sharing) -> CampaignSelector.getCampaigns (inherit) -> WithSharing (override to with sharing) or WithoutSharing (override to without sharing). So basically the sharing inheritance is breaking at the last call – nchursin Nov 28 '20 at 09:53
  • Folks, can somebody tell me what am I missing here? I made a very similar implementation of this example but it simply doesn't work: https://gist.github.com/sauloefo/bdc7fa0500a6674977e7f8744ff7aac8 – Saulo Mar 24 '22 at 20:48
  • If it worth to mention, I'm using this selector from a AuraEnabled method declared in a classed defined as inherited sharing. – Saulo Mar 24 '22 at 20:56