DEV Community

Cover image for Salesforce Apex Triggers
Priyank Sevak
Priyank Sevak

Posted on • Edited on

Salesforce Apex Triggers

I was knee-deep in a Salesforce development project when the dreaded "Too Many SOQL Queries" error reared its ugly head. My code was a tangled mess of DML statements and SOQL queries, and I was staring down the barrel of a governor limit nightmare. That's when I discovered Apex triggers.

What are Apex Triggers?

Imagine invisible guardians watching over your Salesforce data. That's essentially what Apex triggers are. These are blocks of code that execute before or after specific operations on Salesforce objects, like Accounts, Contacts, or even custom objects you've created.

Triggering the Action

Triggers come into play when various actions occur, including:

Insert: A new record is created.
Update: Existing record data is modified.
Delete: A record is permanently removed (remember, Salesforce has a soft delete!).
Undelete: A previously deleted record is restored.

Before vs. After: Choosing Your Moment

The beauty of triggers lies in their timing. We can have triggers execute before an action (insert, update, etc.) or after it's completed. This flexibility allows us to:

Validate Data: Before triggers are ideal for ensuring data integrity by checking custom validation rules or enforcing field-level security. The addError() method lets you prevent invalid saves and roll back unwanted changes.

Automate Workflows: After triggers are great for performing actions that rely on data generated during the initial operation. Think actions like sending notifications, updating related records, or logging audit trails. However, remember, after triggers can only access data in a read-only format.

@future: Keeping Things Asynchronous

Apex triggers are synchronous by default, meaning the program waits for the trigger to finish before continuing. This can impact performance, especially for long-running operations like API calls. But fear not, the @future annotation allows us to schedule asynchronous methods within a trigger, preventing it from blocking the main execution flow.

Triggering Solutions

I quickly realized that potentially, using triggers incorrectly might be the issue.

Reduce SOQL Queries: By bulkifying operations and minimizing unnecessary queries, I managed to stay well within the limits. Triggers execute on the batches of 200 records at a time.

Optimize DML Statements: By grouping DML operations efficiently, I prevented hitting the dreaded DML limit. Salesforce only allow 150 DML calls in one transaction.

A Real-World Example

Let's say I was building a custom object to track product reviews. Every time a new review was created, I needed to calculate an average rating for the product. This involved querying all reviews for that product and performing calculations. A recipe for disaster in terms of governor limits!

Instead, I created an Apex trigger on the Review object. Whenever a new review was inserted or updated, the trigger would:

1.Query all reviews for the product (bulkified to avoid excessive queries).
2.Calculate the average rating.
3.Update the Product record with the new average.

By handling this logic within the trigger, I significantly reduced the number of SOQL queries and DML operations required in other parts of my code.

here's the code I came up with:

trigger ReviewTrigger on Review (after insert, after update) {
    // Group reviews by product Id
    Map<Id, List<Review>> reviewsByProduct = new Map<Id, List<Review>>();
    for (Review r : Trigger.new) {
        if (!reviewsByProduct.containsKey(r.Product__c)) {
            reviewsByProduct.put(r.Product__c, new List<Review>());
        }
        reviewsByProduct.get(r.Product__c).add(r);
    }

    // Calculate average rating for each product
    List<Product> productsToUpdate = new List<Product>();
    for (Id productId : reviewsByProduct.keySet()) {
        List<Review> reviews = reviewsByProduct.get(productId);
        Decimal totalRating = 0;
        for (Review r : reviews) {
            totalRating += r.Rating__c;
        }
        Decimal averageRating = totalRating / reviews.size();

        Product product = new Product(Id = productId);
        product.Average_Rating__c = averageRating;
        productsToUpdate.add(product);
    }

    // Update product records with average rating
    if (!productsToUpdate.isEmpty()) {
        update productsToUpdate;
    }
}
Enter fullscreen mode Exit fullscreen mode

Buy Me A Coffee

Top comments (0)