How about Rollback in AL?

  • Post category:AL
  • Post comments:4 Comments
  • Reading time:8 mins read

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.

The infamous 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:

ASSERTERROR BEGIN
// ... do something
IF ShouldCommit() THEN
COMMIT();

ERROR('');
END;

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 AssertError, really…

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 Rollback

We have 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 COMMIT TRANSACTION.

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.

Pretty please?

Vjeko

Vjeko has been writing code for living since 1995, and he has shared his knowledge and experience in presentations, articles, blogs, and elsewhere since 2002. Hopelessly curious, passionate about technology, avid language learner no matter human or computer.

This Post Has 4 Comments

  1. Michal Cesek

    Hi Vjekoslav, imho you cannot trully live without commits when providing asynchronous transactions such as the API integration. Considering ACID management when connecting NAV/BC and external systems is beyond (my) reality. 🙂

  2. Thomas

    For logging purposes, I´ve created a codeunit, which takes any record (variant) as a parameter and commits it within a StartSession().
    Good enough to ensure a dataset is written, unless an error (rollback) appears or not. And it´s explicit!
    To have an implicit rollback does not seem to be the right way, to handle such kind of functionality.

Leave a Reply