I’ve never truly understood why we could explicitly commit a transaction, but we could only implicitly roll one back. There is a universe of difference between throwing an error (and ending the call stack), and rolling back (and continuing execution).
There was always a way to roll back and go on, sure. Wrap the entire thing in a
if Codeunit.Run() block, throw an error as the last thing inside that codeunit, and there you go. Problem solved. Well, not quite.
Problems with Codeunit.Run
The first problem with
if Codeunit.Run() approach is that at the time you call it, you must not already be inside a transaction. The second problem is that you if the rollback inside the codeunit was conditional, the only way was to defer the condition checking to the codeunit itself. You couldn’t invoke the codeunit *and* then perform the condition checking. If we ever had delegates in AL then we could do something like
if Codeunit.Run(ConditionDelegate), but we don’t have delegates, and you don’t want to get me started on delegates 😁. Yes, you could have double-wrap the whole thing into yet another
if Codeunit.Run() that then performs the condition checking and rolls back if necessary, but how far does this scale?
Really, by forcing people to do this, you are truly fostering extremely monolithic architectures. I can’t say for sure, but I do have a very slight hunch that we ended up having five-thousand-line codeunits as a consequence of not being able to fully control a transaction.
Now – I am not arguing that we need total control, like SQL does. I am merely arguing that we *always* needed a rollback.
AssertError Error('') hack…
Ever since it was first introduced it NAV 2009 SP1, people started abusing
ASSERTERROR ERROR('') construct to roll back a transaction and go happily on. I’ve done a huge number of trainings and coaching sessions and a reviewed mountains of code, all of which showed me that people do this to roll back a lot. People have even done things like this:
// ... do something
IF ShouldCommit() THEN
This was an anti-pattern – I absolutely have to say that! However, it was the only true way for us to properly structure code around at least some kind of transaction control when we needed it. You didn’t have to wrap things into clumsy if-codeunit-runs, you didn’t have to defer conditionality of it, you didn’t have to make sure you were not already in a living transaction before you start the
ASSERTERROR block. But you still could roll back your transaction.
Microsoft frowned on it, they were unhappy we were doing that, they were saying we should not be doing that, but they never provided anything close to a solution.
ASSERTERROR was intended to be used inside test code, but Microsoft never took any active steps to prevent anybody to not use it from there.
… that does not work anymore
Except when AL and AppSource came along, Microsoft made a decision to put a stop on usage of
AssertError (now switching to CamelCase because from this point on I am talking exclusively from AL perspective) from production code. You can use it on-prem, but you cannot use it to deploy your app to AppSource.
Microsoft’s explanation is lazy: “AssertError must be used only in test codeunits, because it creates its own transaction scope. This can lead to unexpected behaviors when used in production code.”
“Can lead…” Well, does it, or does it not? It either “leads” or “does not lead” to problems. It cannot “can lead”. SQL Server that’s sitting underneath does not care if we are inside a test execution context or not. BC’s testability framework will create nested transactions on SQL Server level for each
AssertError, and SQL Server can certainly handle nested transaction – production, or no production.
If Microsoft says that
AssertError is unstable in production code, then it’s equally unstable in test code. It does exactly the same thing on SQL Server level regardless of the execution context. So if it “can lead” to unexpected behavior when used in production code, I argue that it equally “can lead” to the same unexpected behavior when used in test code. I also argue that it *does not* lead to any unexpected behavior. Both C/AL and AL runtimes handle
AssertError correctly on SQL Server level, and if it works for one, it will work for the other, too.
We don’t need
But, I am not arguing for the
AssertError here. As I said – it was only a hack, a workaround, a desperate reach for a tool that was available when we needed it, and didn’t have the right one. Figuratively speaking, we’ve been using a wrench to nail down nails, because we didn’t have a hammer. Now the wrench is taken away, and I am not arguing that we should get the wrench back. No – we still need that hammer. And the hammer is: Rollback.
There was already a request for it on GitHub. Microsoft considered it, but then replied that “there are currently no plans to add the Rollback function.” But why? What’s the problem with Rollback?
… we just need a
Commit. What it does is it commits a transaction. It makes
@@TRANCOUNT equal to 0. A
Rollback would do the same, except it would roll everything back. Okay, I may be oversimplifying things,
Commit doesn’t just do a
COMMIT TRANSACTION on SQL level – it certainly has some housekeeping to do about transaction state at the AL runtime level. Likewise,
Rollback wouldn’t need to do anything else than a
Commit does, except that it should issue a
ROLLBACK TRANSACTION instead of
And yes – maybe I am still oversimplifying things – but we did have a functional rollback already –
ASSERTERROR ERROR('') did the task: it rolled back the transaction.
So, Mr. Microsoft, can we please get
Rollback in AL? And if not – how about a whitelisted
Rollback Management codeunit that does ony
ASSERTERROR ERROR('')? Anything would be so much better than having to rely on if Codeunit.Run() that requires a
@@TRANCOUNT to be 0 and doesn’t effectively do anything so much different than send a
ROLLBACK TRANSACTION to SQL when necessary, implicitly. Just let us do the same – explicitly.