6

I hit this platform problem a while ago Why is this ContentNote trigger receiving 1 record at a time even though multiple records are being inserted?. But today I've hit it again and now I need a workaround.

It appears that ContentNote is a facade over the ContentObjects. I am directly inserting ContentNote objects. Does working with the underlying Content Objects instead (e.g. ContentVersion) avoid the trigger problem? Or is there some other way to get triggers called in bulk for ContentNote?

(I'd like to stay with the trigger approach as customers are used to the synchronous behavior with (older) Notes and it would be good to get the Enhanced Notes behaving in the same way.)

Some context: a trigger is used to enforce editing rights (and generate some custom event records) and we have some deep clone logic that can result in many notes being copied at once. Querying and DML is needed in the trigger, so when that logic is invoked one record at a time for many records, governor limits are hit.

  • Could you add some context on why you want to do so? – Adrian Larson Oct 24 '18 at 14:48
  • @AdrianLarson Sure, I’ve added a context paragraph at the end of the question. – Keith C Oct 24 '18 at 14:53
  • What I'm saying is, why is it a problem? It's hard to know what a good workaround would be without knowing more specifically what you are working around. Would it work to shift some of the logic into async? A Schedulable job seems like it may well work for your needs. – Adrian Larson Oct 24 '18 at 15:10
  • @AdrianLarson The work being done is to support the same functionality for Content (Enhanced) Notes that has been in our product for 5 years for (original) Notes. For the latter, triggers behave as you would expect: a bulk insert results in a bulk call to the trigger. For the former, triggers behave in a surprising way: a bulk insert results in many calls to the trigger with a single record for each call. Given that Salesforce support pretty much said "that's the way it is", I'm wondering if there is a workaround where triggers ancontinue to be used. I'll make that clearer in the question. – Keith C Oct 24 '18 at 15:38
  • 1
    Just to add this is same behaviour you have when you try to deploy content asset in bulk via Metadata API – Pranay Jaiswal Oct 26 '18 at 16:33
1

This answer only relates to workarounds where the calling code can be modified: it is not a general solution to the question. It would be great if there was a pattern that caused the platform to behave in the normal pattern of bulk calling a trigger.

Thanks to Adrian's comments, I realized that my problem of:

  • bulk update in controller
  • platform layers convert into multiple single records calls to trigger
  • my (already bulkified) trigger does queries for every single record call (as it is only passed a record at a time) and so hits governor limits

could be solved by leveraging the already present bulkification of the trigger:

  • turn triggers off (through a pre-existing static boolean) in controller
  • bulk update in the controller
  • directly call the trigger's (already bulkified) handler class from controller passing the records in bulk

which has meant no refactoring necessary for the trigger/handler and instead this small change to the controller:

Triggers.areDisabled = true;               // Added code
SObject[] newNotes = new SObject [] {};
...
insert newNotes;
Notes.handle(                              // Added code
    TriggerOperation.AFTER_INSERT,
    null,
    newNotes,
    null,
    new Map<Id, SObject>(newNotes)
);
Trigger.areDisabled = false;               // Added code
0

My suggestion (if your data volume is low enough) is to leverage lazy loading and cache all the records you could possibly need. There are obviously a variety of mechanisms to do so. For example we often whip up a specific ProfileCache, RecordTypeCache, etc. Below is a very simple example of the multitude of options available.

public with sharing class ProfileCache
{
    public Profile get(Id profileId)
    {
        if (records == null)
        {
            records = new Map<Id, Profile>([SELECT Name FROM Profile]);
        }
        return records.get(profileId);
    }
    static Map<Id, Profile> records;
}

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.