Are you an LLM? You can read better optimized documentation at /after-undelete/add-ons/related-query.md for this page in Markdown format
AfterUndelete.RelatedQuery
Queries other records once per chunk, such as children, siblings or configuration. Your handler reads them by key, with no SOQL per record.
Interface
apex
public class AfterUndelete {
public interface RelatedQuery {
Map<String, AfterUndelete.RecordsProvider> queryRelatedOnAfterUndelete();
}
}queryRelatedOnAfterUndelete(): called once per chunk, at this handler’s turn, before its first predicate. Returns provider name →RecordsProvider.
RecordsProvider
apex
public class AfterUndelete {
public interface RecordsProvider {
List<SObject> query(TriggerHandler.UndeleteRecords 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 ContactWriter implements AfterUndelete.Writer, AfterUndelete.RelatedQuery {
public Map<String, AfterUndelete.RecordsProvider> queryRelatedOnAfterUndelete() {
return new Map<String, AfterUndelete.RecordsProvider>{ 'accountContacts' => new AccountContactsProvider() };
}
public Boolean writeOnAfterUndeleteWhen(TriggerHandler.UndeleteRecord record) {
return record.isNotNull(Contact.AccountId);
}
public void writeOnAfterUndelete(TriggerHandler.UndeleteRecord record, TriggerHandler.UnitOfWork unitOfWork) {
Id accountId = ((Contact) record.getNewSObject()).AccountId;
Integer contactCount = record.getRelated('accountContacts').getAllWhereKeyEquals(accountId).size();
unitOfWork.toInsert(new Task(WhatId = accountId, Subject = 'Contacts on the account: ' + contactCount));
}
private with sharing class AccountContactsProvider implements AfterUndelete.RecordsProvider {
public List<SObject> query(TriggerHandler.UndeleteRecords 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
- Runs before any predicate.
query(records)gets every record in the chunk and costs its SOQL even when none qualifies. Return an empty list when no record can qualify. - SOQL sees the restored records. Add
Id NOT IN :records.getIds()to look only at other records. - Exact keys. Keys are compared as text, case included. Normalize text keys the same way on both sides.
- Unknown names throw.
record.getRelated('<name>')with a name the handler did not return throwsTriggerHandler.TriggerHandlerException, even with ContinueOnError. - Set sharing on the provider. Its SOQL runs under its own class's sharing keyword. An inner class does not take its outer class's keyword.
