The only AL procedure you need to deal with dimensions!

Handling dimensions in AL can be a hassle, in this video, I show a procedure that is a lifesaver, check it out:

https://youtu.be/asQ3OyOt3bU

In this video, Erik walks through the one AL procedure you truly need to master when dealing with dimensions in Business Central. Rather than getting overwhelmed by the complexity of dimension sets, dimension entries, and all the related plumbing, he demonstrates how a single, well-structured procedure can handle the most common scenario: adding one or more dimensions to a record that may already have existing dimensions.

Understanding How Dimensions Work in Business Central

Dimensions are one of the accounting superpowers of Business Central — users love them and rely on them heavily. But when it comes to working with dimensions in AL code, things can get complicated quickly. There are dimension sets, dimension set entries, global dimensions, shortcut dimensions, and various tables involved.

Let’s break down how dimensions actually work at the data level:

  • When you open a sales order and press Alt+D, you see the dimensions associated with that document — for example, Area, Customer Group, Department, and Salesperson.
  • These dimensions arrived on the sales order through default dimension logic — when you select a customer with default dimensions, they transfer to the sales header. Similarly, items on lines can bring their own dimensions.
  • Under the hood, that specific combination of dimensions is stored as a Dimension Set ID — just a single integer on the sales header record. The combination of four dimensions might be Dimension Set ID 4.
  • If you add two more dimensions (say, Business Group and Office), the system creates a new combination and assigns it a new Dimension Set ID (e.g., 27).

The key insight is this: dimensions are not stored directly on the sales order. Only the Dimension Set ID is stored. When you view dimensions, the system looks up that ID to show you the list. This is important because it means our code needs to work with these sets — we can’t just slap dimension values directly onto a record.

The Core Scenario

The scenario you need to handle again and again in AL development is straightforward:

  1. You have a record (like a sales header) that already has dimensions (a Dimension Set ID).
  2. You need to add or update one or more dimensions on that record.
  3. You need to get a new Dimension Set ID that represents the merged result.
  4. You need to update global dimensions if applicable.

If you can handle this one scenario, you’re a champ at dealing with dimensions in AL.

The Procedure: UpdateDimSetOnSalesHeader

Here’s the complete procedure that Erik builds step by step during the video. It lives on a table extension of the Sales Header:

procedure UpdateDimSetOnSalesHeader(var SH: Record "Sales Header"; var ToAddDims: Record "Dimension Set Entry" temporary)
var
    DimMgt: Codeunit DimensionManagement;
    NewDimSet: Record "Dimension Set Entry" temporary;
begin
    // Step 1: Get the existing dimensions into a temporary record set
    DimMgt.GetDimensionSet(NewDimSet, SH."Dimension Set ID");

    // Step 2: Merge the dimensions we want to add
    if ToAddDims.FindSet() then
        repeat
            if NewDimSet.Get(SH."Dimension Set ID", ToAddDims."Dimension Code") then begin
                // Dimension already exists — update the value
                NewDimSet.Validate("Dimension Value Code", ToAddDims."Dimension Value Code");
                NewDimSet.Modify();
            end else begin
                // Dimension doesn't exist yet — add it
                NewDimSet := ToAddDims;
                NewDimSet."Dimension Set ID" := SH."Dimension Set ID";
                NewDimSet.Insert();
            end;
        until ToAddDims.Next() = 0;

    // Step 3: Get the new Dimension Set ID for the merged combination
    SH."Dimension Set ID" := DimMgt.GetDimensionSetID(NewDimSet);

    // Step 4: Update global dimensions in case any of our dimensions are global
    DimMgt.UpdateGlobalDimFromDimSetID(
        SH."Dimension Set ID",
        SH."Shortcut Dimension 1 Code",
        SH."Shortcut Dimension 2 Code"
    );
end;

Let’s walk through each step in detail.

Step 1: Retrieve Existing Dimensions

The DimensionManagement.GetDimensionSet procedure takes a Dimension Set ID and populates a temporary Dimension Set Entry record with all the dimensions in that set. If the sales header is on Dimension Set ID 4 (with Area, Customer Group, Department, and Salesperson), you’ll get four records in your temporary table.

Step 2: Merge New Dimensions

This is where the core logic lives. We loop through each dimension we want to add (ToAddDims) and check whether it already exists in the current set:

  • If it exists: We update the dimension value code. This handles the case where the sales order already has a “Business Group” dimension but with a different value — we overwrite it with the new value.
  • If it doesn’t exist: We copy the record from ToAddDims into NewDimSet and insert it. We set the Dimension Set ID to match the current sales header’s set ID so the primary key is consistent.

Step 3: Get the New Dimension Set ID

The DimensionManagement.GetDimensionSetID function is the magic here. You give it a set of dimension entries (in a temporary record), and it returns the Dimension Set ID for that exact combination. If the combination already exists in the database, it returns the existing ID. If it’s new, it creates one. All the heavy lifting of managing dimension set combinations is handled by this codeunit.

Step 4: Update Global Dimensions

This step is critical for resilience. The UpdateGlobalDimFromDimSetID procedure checks whether any of the dimensions in the set happen to be global dimensions and updates the “Shortcut Dimension 1 Code” and “Shortcut Dimension 2 Code” fields accordingly.

Why does this matter? Because your code should not care whether a dimension is global or not. A customer might configure their system so that “Department” is Global Dimension 1 today, but switch it to “Business Group” tomorrow. Your code should handle both scenarios without breaking. By always calling this function, you ensure the global dimension fields stay in sync.

A Practical Example: Auto-Assigning Dimensions from a Job

The source code includes a real-world example of how to use this procedure. When a user selects a Job Number on a sales order, the system automatically adds a “PROJECT” dimension with the job number as the value:

tableextension 50101 "Sales Header" extends "Sales Header"
{
    fields
    {
        field(50100; JobNo; Code[20])
        {
            Caption = 'Job';
            TableRelation = Job."No.";
            trigger OnValidate()
            var
                dims: Record "Dimension Set Entry" temporary;
            begin
                dims.Init();
                dims.Validate("Dimension Code", 'PROJECT');
                dims.Validate("Dimension Value Code", Rec.JobNo);
                dims.Insert();
                UpdateDimSetOnSalesHeader(Rec, dims);
                Rec.Modify();
            end;
        }
    }
}

The pattern is clean: create a temporary Dimension Set Entry record, populate it with the dimension code and value you want to add, insert it into the temporary table, call the procedure, and modify the record. If you need to add multiple dimensions, just insert multiple records into the temporary table before calling the procedure.

Auto-Creating Dimension Values from Jobs

The project also includes a handy companion piece — a table extension on the Job table that automatically creates dimension values whenever a job is modified:

tableextension 50100 MyJob extends Job
{
    trigger OnAfterModify()
    var
        DimValue: Record "Dimension Value";
    begin
        if Rec.Description <> '' then begin
            if not DimValue.Get('PROJECT', Rec."No.") then begin
                DimValue.Init();
                DimValue."Dimension Code" := 'PROJECT';
                DimValue.Code := Rec."No.";
                DimValue.Insert(True);
            end;
            DimValue.Validate(Name, CopyStr(Rec.Description, 1, MaxStrLen(DimValue.Name)));
            DimValue.Modify(true);
        end;
    end;
}

This ensures that whenever a job exists with a description, a corresponding dimension value is available under the “PROJECT” dimension code, keeping the dimension values in sync with your job master data.

Making It More Generic

Erik mentions that you could make this procedure truly generic by passing a RecordRef instead of a specific Record "Sales Header". That way, the same procedure could work for sales lines, purchase headers, journal lines, or any other table that uses dimensions. You’d use FieldRef to locate the “Dimension Set ID”, “Shortcut Dimension 1 Code”, and “Shortcut Dimension 2 Code” fields dynamically.

In practice, though, Erik notes that most developers tend to copy and adapt this procedure for each specific context where they need it — and that’s perfectly fine for most solutions.

Handling Dimension Priority

One area where you might want to extend this procedure is dimension prioritization. Standard Business Central supports the concept that when the same dimension comes from two different sources, one should take priority. In the current implementation, the “to add” dimensions always win — if a dimension already exists, its value gets overwritten.

Depending on your requirements, you might want to:

  • Skip updating a dimension if it’s already present (preserve the original value)
  • Implement priority logic based on a setup table
  • Conditionally overwrite based on whether the existing value is blank

These are all variations you can build into the merge loop in Step 2.

Summary

Dealing with dimensions in AL doesn’t have to be intimidating. The core pattern boils down to roughly 20 lines of code:

  1. Get the existing dimension set into a temporary record.
  2. Merge your new dimensions into that temporary record (handling both inserts and updates).
  3. Use DimensionManagement.GetDimensionSetID to get the new Dimension Set ID.
  4. Use DimensionManagement.UpdateGlobalDimFromDimSetID to keep global dimensions in sync.
  5. Modify the record.

The DimensionManagement codeunit does all the heavy lifting of managing dimension set combinations and IDs. Your job is just to build the right temporary record set and let it do its thing. Variants of this procedure will appear across your codebase whenever you’re automating dimension assignment — whether it’s importing data, applying dimensions from setup tables, or syncing dimensions from related entities like jobs.