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;
}
}
Top comments (0)