I have three custom objects: Book, Loan, and Reader. Loan is a junction object with lookup relationship with both Book and Reader. The diagram below shows their relationship:

enter image description here

I want to create a trigger on the Loan object that should only allow the creation of loan record only the value of the subject field from the Book object is similar to the value of the subject field from the Reader object. Show error message if the value of both fields are not equal.

This is my code:

trigger LoanTrigger on Loan__c (before insert) {
    for (Loan__c myLoan : [SELECT Name,Book__r.Name, Book__r.Subject__c,
                           Reader__r.Name, Reader__r.Subject__c FROM Loan__c]) 
        if(myLoan.Book__r.Subject__c != myLoan.Reader__r.Subject__c) 
            myLoan.addError('BOOK AND READER SUBJECTS MUST BE SAME');

I get the following error when trying to save loan record:

LoanTrigger: execution of BeforeInsert caused by: System.FinalException: SObject row does not allow errors Trigger.LoanTrigger: line 8, column 1

  • You query all loans, rather than just those that are being inserted. Why?
    – Phil W
    Jul 10, 2021 at 22:52
  • Also note that during before insert there are no IDs for the Trigger.new records, so you have to use a quite different way to query the data from the related objects than the query you have here. Collect the unique IDs of the book and reader records referenced then query these (two separate SOQL statements) dropping them into Map<Id, TheType__c> maps, then simply use these maps to get the required values for each Trigger.new record, applying your logic as needed.
    – Phil W
    Jul 10, 2021 at 22:56
  • Why does it need to be an apex trigger? Seems like a validation rule would work as you can access the parent objects' fields. Jul 10, 2021 at 23:17

1 Answer 1

  1. It makes sense to only query the inserted loan-related data, but you query all loans.
  2. Since this is before insert, you cannot query in the way you have. The inserted loans only get IDs allocated in after insert, which is too late for what you want to do.

Change the trigger to:

trigger LoanTrigger on Loan__c (before insert) {
    Set<Id> bookIds = new Set<Id>();
    Set<Id> readerIds = new Set<Id>();

    // Find all unique referenced books and readers
    for (Loan__c loan : Trigger.new) {

    // Load the unique book and reader records so we have access to the subjects
    Map<Id, Book__c> booksById = new Map<Id, Book__c>(
        [SELECT Id, Subject__c FROM Book__c WHERE Id IN :bookIds]);
    Map<Id, Reader__c> readersById = new Map<Id, Reader__c>(
        [SELECT Id, Subject__c FROM Reader__c WHERE Id IN :readerIds]);

    // Check that the newly created loans have matching book and reader subjects
    for (Loan__c loan : Trigger.new) {
        if (booksById.get(loan.Book__c).Subject__c !=
            readersById.get(loan.Reader__c).Subject__c) {
            loan.addError('BOOK AND READER SUBJECTS MUST BE SAME');

Note that I have assumed the Book and Reader fields are MasterDetail and therefore cannot be null.

This also doesn't prevent either the book or reader subject values changing later, so the valid loan criteria can subsequently and quietly become broken.

  • Thanks Phil. That works fine
    – Hari Ha
    Jul 11, 2021 at 0:23

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .