With this blog post, Microsoft announced the March update of the Developer Preview. The usual Docker images will be publicly available on April 2nd and will contain the Microsoft Dynamics 365 Business Central release candidate.
In the blog post Microsoft listed a number of nice improvements and a lot of fixed issues. However, one of the improvements didn’t make it to the blog post, maybe because it was also discussed in one of the issues on GitHub.
Anyway, it is an import feature, so I’m going to explain it here. The tableextension object has four new triggers:
What are these triggers and how are they related to the OnInsert / etc. triggers in the base table? And what’s the difference with the already existing OnBefore and OnAfter triggers in the tableextension object?
The difference is that these new triggers will be executed before the data of the table has been written to the database. While the OnAfter triggers will be executed after the data has been written to the database.
Let me explain the background.
Back in NAV 2016 we got database events that we can subscribe to. Those events are executed after the database operation. If you want to update data in the record, you have to use MODIFY again, otherwise data will not be saved to the table. To read more about this behavior, I can recommend this in-depth blog post from Vjeko.
With AL this behavior has not changed. The database events are still being raised after the database operation. However, with AL we also got the tableextension object with triggers like OnAfterInsert, etc.
What’s the difference between a database event subscriber in a codeunit and a trigger in a tableextension? There is just one difference: a trigger in a table extension runs only when the RunTrigger parameter is TRUE. While an event subscriber in a codeunit always run. In other words: Rec.Insert(true) will execute both OnAfterInsertTrigger in the tableextension and the event subscriber in the codeunit, while Rec.Insert(false) will only execute the event subscriber in the codeunit. But both will execute after the real database operation!
So we still need to do Rec.Modify in the tableextension trigger to preserve data in the record. Which can potentially lead to unintended looping between insert and modify triggers and event subscribers.
So here is where the new triggers in the tableextension object kick in. These new triggers execute before the database operation. Like the OnAfter triggers in the tableextension, they only run when RunTrigger is true.
With so many triggers and events, I thought it would be a good idea to compose a list with the correct order in which they are executed. So here is a table for the database insert (same applies to modify, delete and rename of course):
|Order||Item||Usage||Only with Insert(True)|
|1||OnBeforeInsert event||Event subscriber in codeunit|
|2||OnBeforeInsert trigger||Trigger in tableextension||X|
|3||OnInsert trigger||Trigger in table||X|
|4||OnInsert trigger||Trigger in tableextension||X|
|5||OnBeforeOnDatabaseInsert (codeunit 1)||Event subscriber in codeunit|
|6||OnAfterOnDatabaseInsert (codeunit 1)||Event subscriber in codeunit|
|8||OnAfterInsert trigger||Trigger in tableextension||X|
|9||OnAfterInsert event||Event subscriber in codeunit|
I think that the OnAfter triggers in tableextensions are now getting less important, to not say quite useless. The new triggers are very close to writing extra lines of code to the end of the C/AL triggers in the base tables (not 100% the same, some restrictions apply, see below). In C/SIDE you don’t write a MODIFY in an OnInsert trigger, do you? The new tableextensions triggers don’t require a modify either, so that is a great improvement. In my view, the new triggers are what the OnAfter triggers should have been in the first place. Ok, I know that the logic behind OnAfter is: after something is completed, so it should occur after the real database operation (that’s the logic Microsoft follows). But in my mind, and I think in the minds of many NAV developers, OnAfterInsert means: after OnInsert trigger of the table, but still before any database operation. Just because with C/SIDE we do the same when we add code to the end of the OnInsert trigger.
To make the OnAfter triggers less useless, one could set a rule like: put code that modifies fields in the table in the new tableextension triggers and all other code should go in either the OnAfter triggers or an event subscriber in a codeunit. But then again, I would rather go with an event subscriber because then I know that the code will always run, regardless of the RunTrigger parameter. But it also depends on the scenario of course.
Anyway, you probably want to move your code from the OnAfter triggers or the event subscribers to these new tableextension triggers to avoid the unintended endless loop that can be caused by using Rec.Modify.
Finally, I would like to express my support to this issue on GitHub: Access to local variables and functions in table and pageextension. Enabling access to local functions in tableextensions would make those new OnInsert etc. triggers even more useful!