Use CreateSequentialGuid for key fields
When a new record needs a GUID primary key, the natural reach is CreateGuid(). Random GUIDs scatter values across the SQL index B-tree, causing page splits on every insert. At scale — bulk imports, high-throughput journal posting — this fragmentation degrades insert, read, and update performance by 20–40%.
This rule tracks the flow of CreateGuid() values through variable assignments and procedure calls (within the same module) to determine whether the generated GUID ultimately lands in a key field. Replace CreateGuid() with Guid.CreateSequentialGuid(), which produces partially-sequential values that keep index inserts append-only.
Example
codeunit 50100 MyCodeunit
{
procedure InsertRecord()
var
MyTable: Record MyTable;
begin
MyTable."Entry Id" := CreateGuid(); // Use CreateSequentialGuid for key fields [PC0029]
MyTable.Insert();
end;
}
table 50100 MyTable
{
fields
{
field(1; "Entry Id"; Guid) { }
field(2; Description; Text[100]) { }
}
keys
{
key(PK; "Entry Id") { }
}
}Use Guid.CreateSequentialGuid() instead:
codeunit 50100 MyCodeunit
{
procedure InsertRecord()
var
MyTable: Record MyTable;
begin
MyTable."Entry Id" := Guid.CreateSequentialGuid();
MyTable.Insert();
end;
}
table 50100 MyTable
{
fields
{
field(1; "Entry Id"; Guid) { }
field(2; Description; Text[100]) { }
}
keys
{
key(PK; "Entry Id") { }
}
}Exception
The rule does not flag the following scenarios:
CreateGuid()assigned to a Guid field that is not part of any key.CreateGuid()assigned to fields in temporary tables (no SQL backing, so no index fragmentation).CreateGuid()passed as an argument to event publishers (idiomatic AL pattern for correlation IDs).CreateGuid()passed to procedures in external dependencies where the source code is not available.
If you intentionally need a random GUID in a key field — for example, to prevent callers from guessing adjacent values in a public API — suppress the diagnostic with a pragma:
#pragma warning disable PC0029 // Public-facing ID must be unpredictable
MyTable."Public Token" := CreateGuid();
#pragma warning restore PC0029Configuration
By default, this rule only flags CreateGuid() calls whose value flows to a field that is part of a table key (primary or secondary). This conservative scope targets the cases with the clearest performance benefit: key fields back SQL indexes, and random GUIDs fragment those indexes.
Sequential GUIDs can improve performance beyond just key fields. As Duilio Tacconi points out
, replacing all CreateGuid() calls with Guid.CreateSequentialGuid() is a straightforward performance win in most scenarios.
The reason this rule doesn’t flag everything by default is a security consideration. As Stefano Demiliani notes , sequential GUIDs are predictable. If you expose GUIDs publicly (in URLs, APIs, or external integrations), a caller could guess adjacent values. Random GUIDs provide better obfuscation of data patterns in those scenarios.
To flag all CreateGuid() calls regardless of whether the value ends up in a key field, add the following to your alcops.json:
{
"UseSequentialGuidScope": "AllGuidFields"
}