Everyone that codes in AL might have at some point forgotten to add Modify() after changing the record.
You might’ve also forgotten to add ApplicationArea to a field and wonder why it doesn’t show up on the page.
Those are common mistakes, that usually don’t take a lot of time to figure out, and are well known.
This article won’t focus on those, instead let’s take a look at problems that are more rare, or harder to diagnose.
I’ll try to update this article as the list grows and I find more uncommon situations.
Modifying fields that are being sorted on
This pitfall may be obvious to some, but sorting on a field that will be modified may interfere with the progression of a loop. Let’s show it on an example.
Imagine we want to go through the list of Customers and change their status to blocked as part of a batch action. After some quick thinking we write the following code:
page 50000 CustomerListExt extends "Customer List"
{
// ...
actions
{
addlast(Processing)
{
action(SetAsBlocked)
{
ApplicationArea = Basic, Suite;
Caption = 'Set as Blocked';
ToolTip = 'Blocks selected non-blocked customer, making them unavailable for new documents.';
Image = Block;
trigger OnAction()
var
Customer: Record Customer;
begin
CurrPage.SetSelectionFilter(Customer);
Customer.SetCurrentKey(Blocked);
Customer.SetRange(Blocked, Customer.Blocked::" ");
if not Customer.FindSet() then
exit;
repeat
Customer.Blocked := Customer.Blocked::All;
Customer.Modify();
until Customer.Next() = 0;
end;
}
}
}
// ...
}
Quick quiz. What will happen when we run this action?
- All selected customers will be marked as blocked
- Only the first selected customer will be marked as blocked
- The action will result in an error “Dataset was invalidated”
The correct answer would be the second option.
What happens under the hood is that AL modifies the dataset we selected with FindSet() method and must recalculate it to keep it sorted.
In the process the record that was previously first in the dataset is now last which causes the Next() method to return 0 and end the loop.
Below is a crude text representation of what happens.
DATASET BEFORE FIRST MODIFY
- Record 1 - Blocked = " " <- our record after findSet
- Record 2 - Blocked = " " <-- the record that will be selected after Next() method
- Record 3 - Blocked = " "
DATASET AFTER FIRST MODIFY
- Record 2 - Blocked = " "
- Record 3 - Blocked = " "
- Record 1 - Blocked = All <- our record after modify
<-- the record that will be selected after Next() method
There are some questions that you may have thought about.
- Why would you sort this set that way?
Sometimes you need to modify the set in specific order. But it may also be done as a mistake or moved from earlier version of NAV, where setting the sort order could often improve performance. - Why not use
ModifyAll()?
That would work in our example case, but often you need to do something more complex with the record, which makesModifyAll()unavailable. - What if we replaced the
Next()withNext(-1)?
That would actually work for this code. Since the record goes to being the last record in the set because we modify from ” ” (enum value 0) to All (enum value 3), going to the previous record would land us back in the dataset.
All in all it is best to avoid sorting and modifying the same field.
Tri-state operator return type
Once when using tri-state operator (<condition> ? <value_if_true> : <value_if_false>), I came upon an unexpected error.
I had the following code in my procedure:
procedure SomeProcedure()
var
// ...
LongerCode: Code[35];
ShorterCode: Code[20];
Result: Code[35];
Condition: Boolean;
// ...
begin
// ...
Result := Condition ? ShorterCode : LongerCode;
// ...
end;
This code is pretty self-explanatory. I wanted to check for something and return ShorterCode if condition was true and LongerCode otherwise.
But when the Condition was false, the code returned an error about the length of the string being too long (assigning Code[35] to Code[20]), which was strange since the Result was of type Code[35], so it should cover both Code[20] and Code[35].
The problem disappeared when I refactored the tri-state operator to an equivalent if else statement.
What I suspect (since I do not have insight into the inner workings of the al compiler) the problem to be is that tri-state operator works like a mini procedure, and it infers its return type from the value declared in the true case.
So when the condition is false, it tries to pass a longer value (above 20 characters) to a smaller type and throws an error.
If you also happen to chance upon this error, follow in my footsteps and refactor to an if else statement.
Time and DateTime differences in storing
Working with dates and times is always a daunting task for a programmer. You need to consider possible formats, timezone differences and client interpretations of them.
In AL, we have a decent support with types like DateTime, Date, Time and even Duration.
However you might not have known that types DateTime, Date and Time are stored in an sql server under the same type, namely datetime.
What’s more they have a peculiar way of storing those values.
Let’s start with Date. In SQL it is stored as a datetime with time part equal to 00:00:00.000, and date part equal to stored date.
When the date in question is a closing date, the time part changes to 23:59:59.999.
Then we have the Time. This time (pun intended) the date part is equal to 1754-01-01, and the time part is equal to stored time.
Lastly DateTime. This type uses both parts of the datetime type, but stores it in a UTC timezone, so if you saved a value 2026-03-03 19:30 +0100.
The value stored in the database would be 2026-03-03 18:30:00.000. This may concern you since if you stored a value coming from an external source (for example an API) that already was in UTC.
If you didn’t take that into account you might change the datetime to an undesired value.
If you want to make sure the time saved is exactly the time coming from an external source, use the Time type.
Alternatively when using Evaluate procedure, make sure the datetime is in UTC format (with letter Z or +0000 at the end).
For now those are all the pitfalls I came up with and deemed interesting enough to write about. Thanks for reading till the end.