Use ReadIsolation instead of LockTable
LockTable() does not acquire a lock. It marks the table so that every subsequent read of that table in the transaction, on any record variable, uses the SQL UPDLOCK hint until the transaction commits. An event subscriber, a FlowField calculation, or unrelated code further down the call stack ends up locking rows it never intended to touch.
If
Record.LockTableis called on anItemrecord, all reads against that table will be done with theUPDLOCKhint, not just the variable it was called on.
— Performance Articles for Developers on Microsoft Learn
ReadIsolation, introduced in Business Central 2023 wave 1 (v22), sets the isolation level on the specific record variable only. Other variables of the same table remain unaffected. The two are not equivalent, and that difference is the point: ReadIsolation keeps lock scope tight and predictable.
There is a second reason to make the switch. Tri-state locking
(introduced in version 23, enabled by default, and always on from version 26) performs reads after writes optimistically, and a single LockTable() switches the table back to pessimistic locking for the rest of the transaction:
Explicitly using the LockTable method in code maintains the same behavior, disabling optimistic reads.
— Tri-state locking in database on Microsoft Learn
Replacing LockTable() with ReadIsolation preserves the tri-state benefits: fewer locks, higher concurrency, and fewer lock timeouts across the system.
Example
The following procedure locks the entire G/L Entry table for the rest of the transaction just to read the last entry number:
procedure GetNextEntryNo(): Integer
var
GLEntry: Record "G/L Entry";
begin
GLEntry.LockTable(); // Use ReadIsolation instead of LockTable [LC0031]
GLEntry.FindLast();
exit(GLEntry."Entry No." + 1);
end;With ReadIsolation, only this specific variable acquires the lock. Any other code reading from G/L Entry later in the transaction remains unaffected:
procedure GetNextEntryNo(): Integer
var
GLEntry: Record "G/L Entry";
begin
GLEntry.ReadIsolation(IsolationLevel::UpdLock);
GLEntry.FindLast();
exit(GLEntry."Entry No." + 1);
end;Convert or delete
Microsoft’s own coding guidance
endorses LockTable for exactly one situation: a read that a directly following Insert, Modify, or Delete depends on. One question decides what to do with a flagged call:
- A read of the same table follows, and a write depends on what it returned. Convert: put
ReadIsolation(IsolationLevel::UpdLock)on the instance that performs that read. - No such read follows. The lock protects nothing. Delete the line.
The modern shape for “make sure nobody else took this number” is ReadIsolation on the instance that reads. The Base Application’s Customer table does this in OnInsert:
var
Customer: Record Customer;
begin
Customer.ReadIsolation(IsolationLevel::ReadUncommitted);
while Customer.Get("No.") do
"No." := NoSeries.GetNextNo("No. Series");The same pattern appears in Vendor, Item, Contact, Employee, Bank Account, Resource, and in the Sustainability ESG tables added in 2025.
LockTable in table triggers
A bare LockTable() in OnInsert or OnDelete binds to the same built-in method as MyVar.LockTable(): the compiler models no self-receiver special case, and the runtime effect is the same transaction-wide UPDLOCK on the table. The diagnostic is therefore reported inside triggers as well.
Most trigger LockTable() calls in the Base Application come in two shapes.
No read follows. A journal table locks itself in OnInsert and then reads an unrelated template table. The lock on the journal line table serves no read:
trigger OnInsert()
begin
LockTable(); // Use ReadIsolation instead of LockTable [LC0031]
ItemJnlTemplate.Get("Journal Template Name");
end;Delete the line. The Base Application removed exactly this call from Item Journal Line in version 26:
trigger OnInsert()
begin
ItemJnlTemplate.Get("Journal Template Name");
end;A dependent read follows. An entry table locks itself in OnInsert, then a second instance reads the last number, and the insert depends on that value:
trigger OnInsert()
var
Attachment2: Record Attachment;
begin
Attachment2.LockTable(); // Use ReadIsolation instead of LockTable [LC0031]
if Attachment2.FindLast() then
"No." := Attachment2."No." + 1;
end;Convert the lock to ReadIsolation on the instance that reads:
trigger OnInsert()
var
Attachment2: Record Attachment;
begin
Attachment2.ReadIsolation(IsolationLevel::UpdLock);
if Attachment2.FindLast() then
"No." := Attachment2."No." + 1;
end;The Base Application still contains about 78 LockTable() calls in table triggers. Since version 23.5 it has removed two and added none, and the newer Microsoft apps (Business Foundation, E-Document Core, Subscription Billing, Excise Taxes) contain no LockTable at all.
Code fix
The ALCops: Replace LockTable() with ReadIsolation code fix rewrites the call to ReadIsolation(IsolationLevel::UpdLock) on the same receiver: MyVar.LockTable() becomes MyVar.ReadIsolation(...), Rec.LockTable() becomes Rec.ReadIsolation(...), and a bare LockTable() inside a table or tableextension becomes a bare ReadIsolation(...). It drops the Wait and VersionCheck arguments of LockTable(true, true), because ReadIsolation has no equivalent.
The fix always converts and never deletes. Apply it when a dependent read follows; when the call protects nothing, delete the line instead.
See also
- Record instance isolation level
— Microsoft Learn reference for
ReadIsolationand isolation levels - Prefer ReadIsolation over LockTable for reads
and Do not LockTable in a read-only procedure
— Microsoft’s BCQuality guidance on when
LockTableis still justified - BC Internals: Tri-state locking — The runtime team on the locking model, with the options listed in order of preference
- Locking Scope: Differences between LockTable and ReadIsolation
— SQL-level analysis showing how
LockTableleaks locks to unrelated code - Optimized Locking Feature vs Dynamics 365 Business Central — Tri-state locking, RCSI, and transaction isolation in practice
- Rec.LockTable: Good Practice or Bad Practice? — How a long-standing best practice became an anti-pattern
- Tri-State Locking and Isolation Levels — Overview of two-state vs. tri-state locking and practical tips
- Control Database Locking Behavior
— Walkthrough of the
ReadIsolationmethod and isolation level options