Monday, October 29, 2012

Trigger to disallow deleting parent if children exist

Whenever you have a master-detail relationship, and you try to delete a parent record using the standard Salesforce interface, Salesforce will delete that record and any associated children records.


Let's say you have a parent "Invoice" object and the child is "Line Item".  Each invoice record may have zero or more Line Item child records.


If you want to not let users delete that record, then you'll need to write a trigger.  There are two ways to do that.

If you have been writing stored procedures forever like myself, my normal approach would be to do an SQL like that:

SELECT i.Id, Count(li.Id) AS Counter FROM Invoice__c i INNER JOIN
    Line_Item__c li ON i.Id = li.InvoiceId

Basically what you want to do is to go through each invoice in the parameter list and do a count of how many line items there are.  Unfortunately in SOQL you cannot include aggregate functions in sub-queries (aggregate functions can only be at the root-query level).

So, the next best thing is to just do a list of each Invoice record and associated Line Item records.  The trigger code looks like:


trigger DeleteInvoice on Invoice__c (before delete) 
{
    List<Invoice__c> invoices = [select id, (select name from line_items__r) 
        from invoice__c where
        id in :Trigger.old];

    for (Invoice__c inv: invoices)
    {
        if (!inv.line_items__r.isempty())
        {
            Trigger.oldMap.get(inv.id).addError('error goes here');
        }
    }
}


So what the above does is to first get a list of invoices and associated line items.  Then go through each invoice and see if there are any related line items.  If there are, then provide an error to that invoice.

What I found out is that I might be able to simplify that  little.  Instead of going through the Invoice__c first, I can go through Line_Item__c first, as this following trigger shows:


trigger DeleteInvoice on Invoice__c (before delete) 
{
    List<Line_item__c> lis = [select invoice__c from line_item__c 
        where invoice__c in :Trigger.oldMap.keyset()];

    for (line_item__c li : lis)
    {
          trigger.oldmap.get(li.invoice__c).adderror('error goes here');
    }
}


What this second trigger does is to simplify look at the child table and find those line items where the invoice id matches the invoice ids in the parameter list.  The invoices associated with these line items retrieved are the ones that you do not want to allow deletion.

It looks to me that the second trigger is shorter because it does not have to have the "if" statement.  I don't know if there is any substantial performance improvement, but the code looks somewhat cleaner.

What do you think?


7 comments:

  1. How about creating a new roll up summary field on Invoice__c?

    trigger DeleteInvoice on Invoice__c (before delete) {
    for(Invoice__c inv : Trigger.Old) {
    if(inv.NumberOfLineItems__c > 0) {
    account.addError(Label.InvoiceDeletionErrorMessage);
    }
    }
    }

    ReplyDelete
  2. what is the use of rollup summary...you can directly get the size of by using .size()>0 and show the error..Isin'it??

    ReplyDelete

Please leave a comment.