Use CreateSequentialGuid for key fields (PC0029)

When assigning a new GUID to a field that is part of a table key, use Guid.CreateSequentialGuid() instead of CreateGuid(). Random GUIDs cause SQL index fragmentation because their values are scattered across the index B-tree. Sequential GUIDs are generated in increasing order, which keeps index inserts append-only and reduces page splits 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.

A code fix is available for this diagnostic.

Example

The following code assigns a random GUID to a primary key field:

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") { }
    }
}

To fix this, use Guid.CreateSequentialGuid():

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") { }
    }
}

Scenarios that are not flagged

  • Assigning CreateGuid() to a Guid field that is not part of any key
  • Assigning CreateGuid() to fields in temporary tables (no SQL backing)
  • Passing CreateGuid() as an argument to event publishers (idiomatic AL pattern)
  • Passing CreateGuid() to procedures in external dependencies where the source code is not available

Configuration

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.

However, 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"
}

This is useful for teams that want to audit every CreateGuid() usage and make a conscious decision per call site.

See also