Are you an LLM? You can read better optimized documentation at /before-update/add-ons/related-query.md for this page in Markdown format
BeforeUpdate.RelatedQuery
Loads children, siblings or any other records once per chunk, keyed for fast reads. It replaces a query per record.
Interface
apex
public class BeforeUpdate {
public interface RelatedQuery {
Map<String, BeforeUpdate.RecordsProvider> queryRelatedOnBeforeUpdate();
}
}queryRelatedOnBeforeUpdate(): called once per chunk, at this handler’s turn, before its first predicate. Returns provider name →RecordsProvider.
RecordsProvider
apex
public class BeforeUpdate {
public interface RecordsProvider {
List<SObject> query(TriggerHandler.UpdateRecords records);
String keyOf(SObject record);
}
}query(records): called once per provider, at the handler’s turn, with every record in the chunk. Returns the rows to index by key;nullcounts as no rows.keyOf(record): called once per row thatqueryreturned. Returns the key thatgetFirstWhereKeyEqualsandgetAllWhereKeyEqualsmatch;nullleaves the row out of the index.
Example
apex
public with sharing class ContactPopulator implements BeforeUpdate.Populator, BeforeUpdate.RelatedQuery {
public Map<String, BeforeUpdate.RecordsProvider> queryRelatedOnBeforeUpdate() {
return new Map<String, BeforeUpdate.RecordsProvider>{ 'accountContacts' => new AccountContactsProvider() };
}
public Boolean populateOnBeforeUpdateWhen(TriggerHandler.UpdateRecord record) {
return record.isChanged(Contact.AccountId);
}
public void populateOnBeforeUpdate(TriggerHandler.UpdateRecord record) {
Id accountId = ((Contact) record.getNewSObject()).AccountId;
record.put(Contact.Description, 'Contacts on the account: ' + record.getRelated('accountContacts').getAllWhereKeyEquals(accountId).size());
}
private with sharing class AccountContactsProvider implements BeforeUpdate.RecordsProvider {
public List<SObject> query(TriggerHandler.UpdateRecords records) {
return [SELECT Id, AccountId FROM Contact WHERE AccountId IN :records.getIdsOf(Contact.AccountId)];
}
public String keyOf(SObject record) {
return ((Contact) record).AccountId;
}
}
}Good to Know
- SOQL sees the saved values. A query on the records being updated returns their values from before this update. Two records in one chunk that change to the same email do not find each other; compare them in a Finalizer.
- Exclude the records themselves. To look at other records of the same object, add
Id NOT IN :records.getIds(). - Keys match exactly. Key lookups are case-sensitive. Normalize text keys the same way in
keyOfand in the lookup. - The provider sets the sharing. Its SOQL runs under the provider class's own sharing keyword. Use
without sharingwhen a check must see every record. - Runs on every pass. Providers query for every chunk and every nested update, even when no record qualifies.
