I still see a lot of X++ developers calling the Box class in code that is used in database transactions. It may cause nasty problems – as I show in this post.
Imagine code like this, used in some table:
void delete() { if (Box::yesNo("Are you sure?", DialogButton::No) == DialogButton::Yes) { super(); } }
When a record is to be deleted, processing is paused and the following modal window is shown to the user:
This behavior has several issues – for example, what should happen if no user is available, because the deletion is executed from a batch job? (Server-side batches will fail because the method is bound to client; client-side batches simply get frozen).
But I want to show one more problem that may not be so easy to detect. Let’s test the delete() method with the following code:
static void deleteMyTable(Args _args) { MyTable mt; InventSum inventSum; ttsbegin; mt = MyTable::find("A1", true); inventSum = InventSum::find(mt.ItemId, mt.InventDimId, true); mt.delete(); ttscommit; }
MyTable’s delete() method looks as above (you could use PurchLine or any other with ItemId and InventDimId). In addition to deleting a record in MyTable, another record is fetched from InventSum table.
If you run this code, everything works as expected and the modal box is displayed to you. Now start one more Dynamics AX client and run the same job again, simulating another user. The code will stop on the line fetching InventSum and will stay there unless you click Yes or No in the original AX client. In reality it may happen in less than a second, but also in hours (e.g. the user is away) or even never.
The problem here is that InventSum table (and several others) uses pessimistic locking and we require a write lock while it is still held by the transaction in the blocking client. Therefore other clients must wait for the record to be available. In other words, other clients freeze if they call any code requiring such a lock.
Just a single call to the Box class in a wrong place is able to block all users – and I’ve already seen that. It’s wise to avoid using Box class completely, or at least use it only from forms (where you definitely shouldn’t have any transactions). If you call it in any generally reusable business logic, you’ll never know if somebody uses it in transactions.
And if you really have to ignore all these recommendations, check for application.ttsLevel() and use the Box class only if the transaction level is equal to zero.
The Box class methods ought to test for the ttslevel, and throw an error if called inside a transaction.
That’s… a great idea. The source code is accessible in AOT, so we can implement the logic ourselves. It won’t turn the Box class to something more useful, but it will at least fail fast and won’t allow the blocking issue described above.