Use CreateSequentialGuid for key fields

Properties
PC0029 Info Performance Code Fix Ignore Obsolete

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 PC0029

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.

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

See also