One of great new functional features of Microsoft Dynamics NAV 2016 is preview posting. It allows you to preview all the entries that would result from posting a document or a general journal.
Preview posting is not a simple thing. If it was, Microsoft would have delivered it years ago. There must be something in particular with NAV 2016 that powers preview posting, so I decided to investigate it and see exactly how it works.
I am not particularly happy with what I found out, but I have also learned some valuable lessons from it. In this post, I’ll share my findings.
Functionally, preview posting looks like this:
- You click “Preview Posting” in a sales or purchase document, or in a general journal.
- The posting process runs.
- You get a page that shows the entries resulting from the posting, navigate-style.
- No trace is left in the database, anywhere, that any posting ever happened.
Technically, on high level, it looks like this:
- Preview posting runs using the same code that processes actual posting.
- After posting completes, all resulting entries are collected from actual tables into temporary ones.
- Temporary tables are shown through a special page.
- Transaction is rolled back.
If you know standard NAV code well, then you must ask an obvious question: how does it do it? Sales and purchase posting routines are full of COMMITs. A quick view at codeunit 80 or 90 shows that COMMITs are not called if PreviewMode is TRUE. Apparently, this resolves the problem of running the whole posting in a single transaction that can then be rolled back in the end.
So – this must be all there is to it?
Unfortunately, it isn’t.
Even though it could have been possible to achieve this with at least two different approaches, preview posting actually uses a new feature, which – after a closer look – seems to have been specifically tailored to power the preview posting. Your guess? Yes – TryFunction.
If you look at codeunit 81 (for example; I’ll use sales posting routine for all examples in this post) you can see that its Code function is turned into a TryFunction. It is called directly from the OnRun trigger, but it’s called with the IF NOT Code THEN construct in the Preview function.
Here’s the Preview function:
It does several things:
- It calls function Start from codeunit 19. More about this later. This is the cornerstone of the concept, and it would be much better if it could have been avoided.
- It sets the PreviewMode to TRUE. This flag is then passed on to codeunit 80, which then makes COMMIT decisions based on it.
- It runs the Code function in the try mode. Apparently, it expects the function call to fail.
- If the codeunit 80 fails with anything but a specific expected error message (let’s call it “preview signal error”) then it throws whichever error came from codeunit 80. This covers for all situations where posting cannot succeed for any valid reason, such as customer being blocked, missing posting setup, posting date out of bounds, you name it.
- If the codeunit 80 fails with the preview signal error, then it means posting actually succeeded, we only got this error because we needed the Code to fail in try mode. Codeunit 80 fails with this error after it posts all the entries, and just before it starts cleaning up data and calling functions irrelevant for the preview functionality. This exits the codeunit, without rolling back anything (because it’s in the try mode):
- It calls the Finish function from codeunit 19. This function collects all entries into temporary tables. Since TryFunctions do not rollback on failure, this means that at this point all generated entries still patiently wait in the database. They are collected, and then shown in the special page.
- Transaction is rolled back through silent abort.
This all looks pretty hunky-dory as long as there are no COMMITs anywhere in the code. A COMMIT during the preview posting would be disastrous. But, how do you make sure there is no COMMIT? Well, codeunit 80 carefully goes around COMMITs if it is in PreviewMode, but can it make sure no COMMIT is called somewhere outside its reach? You could easily sneak a COMMIT or two through customization. Also, thanks to the events feature of NAV 2016 as well as through extensions, objects can pop up out of a blue that subscribe to one of the many events and call COMMIT.
Well, you don’t even have to customize anything – there is (what seems like) a bug in codeunit 80 which results in a COMMIT being called even during preview posting.
Now, this surely is a bug, and I am sure in a cumulative update, this code will read:
Anyway, since this COMMIT is in there, all you need to do is switch Calc. Inv. Discount on, release a sales order, and preview post it, and – voilà:
Now, I haven’t seen this error in my entire NAV career. I knew it existed, and it was there to prevent posting unbalanced transactions into G/L, but seeing it on screen would mean you seriously messed up with your code. Now, in the course of a few days, I see it two times.
But why does this error show? It’s certainly unexpected. Knowing that CONSISTENT is called at a single place in codeunit 12, where it marks transaction as inconsistent as long as it doesn’t balance to 0 (which certainly isn’t the case with PreviewPosting) you may be confused and wonder if there is some other CONSISTENT call. A few seconds of investigation will reveal that there is one – right at the beginning of the Start function in codeunit 19:
Here, it unconditionally marks the transaction as inconsistent, right at the beginning of the whole posting process. Why does it do so?
In C/AL transaction is committed to the database in two situations:
- When code execution completes and control returns to the user.
- When C/AL calls COMMIT.
Unless there was an error, transaction will be committed. However, there is one function in C/AL that changes the committing behavior – it’s the CONSISTENT function. You call CONSISTENT on any record variable, and pass a TRUE or FALSE to it. Just before a transaction is committed to the database, the runtime checks if there are any records which have been marked as inconsistent (having received FALSE from the CONSISTENT call). If there are, transaction is rolled back and execution aborted with the error message you saw above.
You may imagine, codeunit 19 never sets CONSISTENT(TRUE) – it simply leaves the record as inconsistent for the duration of transaction. If there are no COMMITs, the transaction will be aborted either by a legitimate posting error, or by the silent abort at the very end. However, this CONSISTENT(FALSE) at the very beginning makes sure that no commits are accidentally called – if they are, the consistency check will result in failure.
And that’s it. No way you can cause accidental catastrophe by injecting a COMMIT or throwing an error at a wrong place.
If you have read my last post, about the dangers of TryFunction, and if you agree with it (and everyone I spoke to agrees) – then something is wrong. If an error happens inside a TryFunction nothing is rolled back. This behavior is dangerous, and yet – without it – preview posting couldn’t work. If data was rolled back, then nothing would be left in the database for the Finish function in codeunit 19 to collect.
As I said earlier, there are at least two approaches to preview posting, without unnecessary (and utterly confusing for end users) CONSISTENT(FALSE), and without excessive refactoring of posting routines – and I’ll explain each of the two approaches in two separate posts, one on Thursday, and one on Friday this week – so stay tuned.
In the meantime, there is one useful takeaway from this whole analysis: there is a way to make sure that failure in any TryFunction does not create mess in the database. It’s pulling the same CONSISTENT trick that Microsoft pulled in the Preview Posting feature, just on a smaller scale.
Now, what I am going to show is ugly, dirty, and I hate even being forced to blog about that. But with the current state of TryFunction feature, this is the best shot:
The mechanics are simple – at the beginning of the TryFunction, you mark the transaction as inconsistent. In case TryFunction fails, the transaction will remain marked as inconsistent in case you forget to rollback the data yourself. If TryFunction succeeds, then transaction is marked as consistent again, so that transaction can be successfully committed, when time is ripe.
However, as I said – this is ugly. Any COMMIT anywhere in the code will slice this approach to ribbons. And this proves once again that TryFunction behavior – as it currently is – of not rolling back data, is absolutely wrong and that any architecture (including Preview Posting) that depends on it, is inherently bad.
Anyway, I hope you learned a thing or two from this post, as have I while digging through the code.
No animals were harmed during the making of this post, even though a few were quite close to it.