12

It is quite possible, and in fact recommended (if you need trigger logic on both events). See also: Trigger Frameworks and Apex Trigger Best Practices Triggers (Apex Developer Guide) A common pattern is to have three separate layers of code: the trigger itself, a handler, and a service layer. Here's the trigger framework I prefer. Trigger trigger ...


9

== is for equality checks, = is for assignment. As such: If (soAccount.ISLock__c = True){ will assign the value of true to IsLock__c, and this statement will always be true. For this reason, I always recommend that you avoid comparing to true/false values, because it can prevent typos like this from compiling. If (soAccount.IsLock__c){


8

You have a typo: Trigger.oldMap.get.(cl.id).Initial_Estimate__c should be: Trigger.oldMap.get(cl.id).Initial_Estimate__c You probably also need to apply an explicit cast, like: ((Claim__c) Trigger.oldMap.get(cl.id)).Initial_Estimate__c You'll find this sort of issue easily by using an appropriate IDE. For example, I found it be copy/pasting your code ...


6

This is a Known Issue that should be fixed in Spring '19. Until then, consider using a List<String> instead (as mentioned in the workaround), or use a Set<Id> instead, which should be more reliable. The reason why it happens is usually because of different log levels depending on how you run the test/deploy. For example, a change set would ...


6

AFAIK there is no way to do this without adding explicit logic. And that explicit logic is pretty ugly e.g. make the WF and PB set database data or static variables that signal they have run and then have the trigger check those. Which couples the pieces together horribly. If possible, write the trigger so it is idempotent i.e. nothing bad happens even if ...


6

Conceivably what you could do is set up a Queueable class. On the first pass through the trigger, instantiate and enqueue it, and then save that in a static variable so you know it's already been created. It will run once after all executions are done, in a separate transaction. The trouble is, once you have enqueued it, you can no longer alter any data you'...


6

This error has to do with the trigger context, not with the literal location of the query in the source code. It looks like your query is located within a trigger handler, meaning it's executed after being called from the trigger and therefore is running in trigger context. So let's look at this query. SELECT id, Vmstar_Id__c, GSS_Duplicate_Case__c, ...


6

This is because you are using an aggregate query on Contact: for (AggregateResult ar: [Select Count(Id) ContactCount, AccountId from Contact where AccountId IN: AccountIds GROUP BY AccountId]) Once the final Contact is deleted, there's nothing to aggregate, so you will not see an AggregateResult with that Contact's AccountId and ...


6

opp.get(metaData.get(0).Opportunity_Field_Name__c) = opp.Account.get(metaData.get(0).Account_Field_Name__c); The result of opp.get() is not an lvalue; it does not represent a location to which you can assign a value on the left-hand side of an assignment statement. Put another way, it returns a value, but not a reference to the value's location. The ...


6

No, you have not properly bulkified your code. Bulkification 101's most important lesson is to aggregate filter values, query before your loop and put records into a Map, and update values after your loop. Set<Id> sponsorshipIds = new Set<Id>(); for (Opportunity record : trigger.new) { sponsorshipIds .add(record.Sponsorship_Contract__c); } ...


6

IMHO the first two of your cited reasons are spot on. Your senior dev's brush off of "chance of errors are very [low]" is entirely naive - simple addition of validation rules by admins could easily bork your DML operation (validation is done between before and after), and the introduction of process builders and flows doubly so. If you don't follow ...


5

You should filter only records that can be matched. You will get some extra results with the below approach, but hopefully fewer than 50k. Also note that NOT IN filters will give you selectivity problems if your table goes over 100k records total. Set<String> emails = new Set <String>(); Set<String> emails = new Set <String>(); // ...


5

In Apex Code, we use == for comparison, while = is for assignment. This means that you're actually trying to assign a value (true) to a field (acc.Use_School_Contacts__c). The system allows this syntax because you can perform an assignment and then use the result of that assignment later. However, in after insert and after update triggers, Trigger.new is ...


5

For the first one, instead of: affectedCasesByContactId.keyset().containsKey(...) use either the Map method containsKey on the Map: affectedCasesByContactId.containsKey(...) or the Set method contains on the Set: affectedCasesByContactId.keyset().contains(...) For the second one instead of trying to access from the List: trigger.new.get(...) access ...


5

It sounds like you are looking for an exit event. If doesn't exist yet, but you can vote for the idea, the description of which is pasted below: Since triggers only handle 200 records at a time, it would be convenient if we could schedule some code that runs in the same transaction as the current transaction, but after all other triggers, recursion, etc ...


5

If I understand your question correctly, then you actually can take your related record update logic and put it in either a before or after trigger. Salesforce's suggestion is that before triggers should be used to update data on the records taking part in the trigger (the ones stored in trigger context variables), but it's just that... a suggestion. Keeping ...


5

This is typically solved with the aggregate-query-update pattern, which I wrote about a few years back. To do so, we follow three steps. 1 Aggregate Gather the values we want to query. Map<String, Id> extIds = new Map<String, Id>(); for(Properties__c record: newPropertiesList) { extIds.put(record.Account_Ref_Ext_Id__c, null); } 2 Query for(...


5

if(checkRecursive.runOnce()){ This is perhaps the worst possible pattern to control recursion, specifically because it causes behavior like what you're observing right now. Here, you seem to have an especially pathological situation where you're allowing the entire trigger to run exactly once per transaction, regardless of the trigger event. That is, the ...


5

Short answer is: Yes, both maps have the same key set.


5

If you don't query something, you don't get it from the database. You need to change your query: list<policy__c> pollist = [ SELECT Close_Date__c, (SELECT Role_Name__c, Contact__c FROM Account_Roles__r) FROM policy__c WHERE Close_Date__c = LAST_N_DAYS:30 ];


5

In some cases, using before update to update related records can be problematic, because if those records then try to recursively update the original records, you'll end up with an error you wouldn't get with an after update trigger; for example, it's entirely possible for a child to update its parent, which in turn updates its children (and then the ...


4

Credit for aggregate result trick of aliasing the field to Id goes to sfdcfox I believe this can all be reduce to: Trigger Sum on Contact(after insert, after update, after delete, after undelete){ Map<Id, Account> accountMap = new Map<Id, Account>(); for (Contact c : trigger.new == null ? trigger.old : trigger.new) ...


4

To get or Query deleted records you need to use ALL ROWS So your query will be List<Object__c> deletedRecords = [SELECT Id FROM Object__c WHERE ID IN :lstTriggerOld AND ID NOT IN : lstAllRecords ALL ROWS]; All Rows Keyword


4

You have one constructor for after insert, after update, after delete actions. But Trigger.old is only available in update and delete triggers. That is why it fails on after insert with null pointer exception. Refer to documentation


4

What can I do here? Is there a way to run all this logic post-commit? You do need to do at least some of this asynchronously, because any other technique is going to end up in deadlock or missing records. In both triggers, build a list of unique identifiers, then pass it in to a Queueable: public class ResolveExternals implements Queueable { String[] ...


4

Trigger.new is a type of List<sObject> outside of a trigger context. You need to "cast" it back in to a List<Opportunity> for it to work: for(Opportunity oppt : (List<Opportunity>)Trigger.new) In addition, Trigger.oldMap is also a Map<Id, sObject> outside of a trigger context. You would have to cast the sobject in to an opportunity ...


4

It is not possible to tell when the future method will run. If you want it to run after your current code is done executing, you have 2 options: Call your future method as the very last step of your execution Create a scheduleable class and schedule it in a couple of minutes, that way you will be sure that your code has enough time to process


4

It's behavior that I wish weren't the case (though having it behave the intuitive way would probably lead to other problems), but it is known and expected. Trigger.old and Trigger.oldMap do not have their values updated when a field update action causes update triggers to be run again. This is covered in the documentation: Trigger.old contains a version of ...


3

Problem is that for Map<Id, Set<String>> for every new Id, value (which is Set should be initialised separately, before records are added into it. For example: Map<Id,String> Uterr = new Map<Id,String>(); List<Territory> terr = [SELECT Name, Id FROM Territory]; for(Userterritory ut : [SELECT UserId, Territoryid ...


3

If you want to update fields on trigger records, you should use before events. From the Apex Developer Guide on Triggers: There are two types of triggers: Before triggers are used to update or validate record values before they’re saved to the database. After triggers are used to access field values that are set by the system (such as a record's Id or ...


Only top voted, non community-wiki answers of a minimum length are eligible