function readOnly(count){ }
Starting November 20, the site will be set to read-only. On December 4, 2023,
forum discussions will move to the Trailblazer Community.
+ Start a Discussion
ChrisDevOzChrisDevOz 

Apex trigger, feedback welcome

Hi All

 

Over the last few months I have been teaching myself Apex.  I have some PHP/javascript experience but apart from that this is all pretty new.

 

I had some live use cases that required the creation of a trigger that I managed to write succesfully and deploy to production.  Everything works ok, but have no idea whether my code is best practice.  I have attempted to write them to allow bulk request and written the appropriate test classes, but beyond that I don't know if they are written well.

 

If any one has any comments/suggestions it would be great to get some feedback.  Also there might be people out there who can benefit from the code.

 

Use Case.

 

When an Account is updated in Salesforce, all child opportunities should be updated with a specific value. This value is created from an external id on the account and a text value on the opportunity.

 

 

trigger updateOpportunityids on Account (after update) {
//soql statement creates a list of all opportunities for the trigger Accounts
List<Opportunity> holdopps = [select id, service_id__c, service__c, account.company_id__c from opportunity where Account.ID IN :Trigger.newMap.keySet()];
// loop through holdopps, assigning each record to the o variable
for (Opportunity o :holdopps) {
// if the service id of the o sobject variable = the combination of the service and company id
if (o.Service_id__c != o.Service__c+o.account.company_id__c && o.account.company_id__c != NULL){
// add the serviceid variable to the current o sObject
o.Service_id__c = (o.Service__c+o.account.company_id__c);
}
}
//Update Opportunities using the holdopps array
try {update holdopps; }
//Exception Handling
catch (DmlException e){
}
}

 

 

And the test class

 

 

public with sharing class updateOpportunityidstest {
static testMethod void testserviceid() {
//create holdopps list
List<Opportunity> holdopps = new List<Opportunity>();

//Create new Accounts
Account a = new Account(Name='A Name', company_id__c = 9999);
Account b = new Account(Name='B Name', company_id__c = 10000);
insert a;
insert b;

//Create Opportunity
Opportunity o = new Opportunity (Name='O Name', Service__c='ii', Service_id__c='', AccountId=a.id);
o.StageName = 'Prospecting';
o.CloseDate = Date.newInstance(2010,06,06);
o.ExpectedUsage__c = 5;
o.Nett_Seat_Fee__c = 5;
insert o;

//Create Error Opportunity
Opportunity p = new Opportunity (Name='P Name', Service__c='ii', Service_id__c='', AccountId=a.id);
p.StageName = '';
p.CloseDate = Date.newInstance(2009,01,01);
p.ExpectedUsage__c = 5;
p.Nett_Seat_Fee__c = 5;

//Update Account
Account c = new Account(Name='B Name', company_id__c = 10001, id = a.id);
update c;
Opportunity q = [select name, Service_id__c from Opportunity where id = :o.id];
system.assertEquals(q.Service_id__c, 'ii10001');
}
}

 

 

That's it, look foward to your feedback!

 

Cheers

 

Chris

bob_buzzardbob_buzzard

Welcome to apex.  

 

Your trigger looks pretty resilient against bulk requests, but I think there's a few efficiency savings/improvements that could be made:

 

(1) If you only need to carry out this change on the opportunities when a specific field on the account changes, it would be better to compare each element of trigger.old/new and create a new list or map only containing those accounts which have a change to the field.

 

(2) You are updating all the retrieved opportunities regardless of whether you actually had to apply a change - I'd be inclined to store those that you have changed in a new list and just update that.

 

(3) You may hit problems if there are a large number of opportunities to update - in that case it may be better to use a SOQL for loop to process the opportunities in batches.  That would also allow you to update the changed opportunities in batches, should you hit governor limits on maximum collection size.

 

(4) You are catching and eating the DMLException at the end - if there's nothing that you can do to improve the situation its best to let the exception bubble up.

 

BritishBoyinDCBritishBoyinDC

One question to throw out there - can you achieve this without a trigger at all? Looks to me like you could just create a Formula field on Opportunity and use a cross object lookup? 

mtbclimbermtbclimber

+1, Welcome to Apex Chris!

 

bob_buzzard has some excellent suggestions there, one note there is no collection limit in Apex (as of API version 18 I believe).  From a code perspective adding his feedback to your solution will certainly tighten things up.

 

That being said, BritishBoyinDC has an interesting point.  As you are doing this on every update of account you seem to be trying to keep the record data in sync and not just "defaulting it" per se.  In that case defining a spanning formula field on Opportunity may be an alternative to code.  There are limitations with formulas that can be avoided, however, by setting the value in code as you are currently pursuing.  If you have formulas that already include this field then the formula complexity limit can be mitigated with the code approach. Also, if you are dependent on the field being an "external id" for integration purposes then it can't be a formula.  Performance of queries that include this field as a filter will also be improved by having a static field vs. a formula, formulas aren't indexed for search, etc.  Also there is a limit around spanning formulas which you may be consuming or looking to preserve.

 

Assuming you need this to NOT be a formula then you have to take something else into consideration (that you may be but just not including in this post) if your goal is sync'ing and that is you'll need handle creation and changes to opportunities.  For this you can probably get away with a workflow field update on opportunity in those events where the accountId has changed as you would need to handle the case where a new opportunity is inserted and when an existing one moves from one account to another.

 

I realize you are new to Apex and within this post I may be incorrectly assuming you know a lot of the terms being thrown around like "spanning formulas", "workflow field updates", etc.  There are a lot of tools at your disposal on the platform and it does help to understand them so you can make the best implementation decisions for your use cases. 

 

If you're new to most of this I recommend the following reading/working through in order of "lowest time investment" to "deepest understanding":

 

  1. Force.com Workbook
  2. Force.com Fundamentals*
  3. Force.com Cookbook

* This book covers the point and click configuration capabilities only.

 

Hope this helps,

ChrisDevOzChrisDevOz

Thanks Everyone for your replies.

Good to know that I'm on the right track and thanks to Bob's feedback I will be able to refine my code.


As for the use case, the service_id__c field updated on the opportunity is used as an external id, so Formula fields are a no go.  It is often the case that the account gets assigned a company id, after the opportunities have been created, so I couldn't use a workflow either.

As you mentioned mtbclimber, I have a workflow on the Opportunity that updates this field should opportunities be created/updated.

I'm lucky to have had a fair amount functional salesforce expierience which is hopefully making learning apex that little bit easier!

Cheers

Chris

ChrisDevOzChrisDevOz

For anyone interested, here is my updated code.

 

1. Changed SOQL query to only reference changed values between trigger old/new

2. Create a list of Opportunities that need updating, rather than updating all opportunities pulled down by the SOQL Query

3. Debug for the DML exception. - could probably put a loop in to make it more robust.

 

 

trigger updateOpportunityids on Account (after update) {
//Map to hold changed Accounts
Map<String, Account> CA = new Map<String, Account>();
//Compare Trigger Old/New and see where the external id has been changed/
for(Account AN : trigger.new){
IF (AN.Company_id__c != System.trigger.oldmap.get(AN.id).Company_id__c){
CA.put(AN.id,AN);
}
}
//soql statement creates a list of all opportunities for the trigger Accounts
List<Opportunity> holdopps = [select id, service_id__c, service__c, account.company_id__c from opportunity where Account.ID IN :CA.keySet()];
//Create a list to hold the updated opportunities - don't need to update all the opportunities pulled down.
List<Opportunity> updateopps = new List<Opportunity>();
// loop through holdopps, assigning each record to the o variable
for (Opportunity o :holdopps) {
// if the service id of the o sobject variable doesn't equal the combination of the service and company id
if (o.Service_id__c != o.Service__c+o.account.company_id__c && o.account.company_id__c != NULL){
// add the serviceid variable to the current o sObject
o.Service_id__c = (o.Service__c+o.account.company_id__c);
updateopps.add(o);
}
}
//Update Opportunities using the update opps list
try {update updateopps; }
//Exception Handling
catch (DmlException de){
System.debug('Update Failed:'+ de.getMessage());
}
}