4 min readRishi

Financial Dimensions in D365 Finance: How They Really Work

Financial dimensions are the part of Dynamics 365 Finance that everyone uses and few truly understand. Cost center, department, business unit, project — they tag every transaction and drive every meaningful report. Then a developer opens the data model, sees DimensionAttributeValueCombination joined to four other Dimension* tables, and quietly backs away. Let us make this concrete.

Two kinds of dimensions

There are exactly two flavours, and confusing them causes most dimension bugs.

  • Default dimensions are a set of dimension values attached to a master record or line — a vendor, a fixed asset, a purchase line. They answer "what tags should transactions inherit from here?"
  • Ledger dimensions are a main account plus its dimensions, combined into one accounting string. They answer "exactly which account did this voucher hit?"

A purchase order line carries a default dimension. When it posts, the framework merges that default dimension with the main account to produce the ledger dimension on the voucher. Same values, different purpose, different storage.

Where the values actually live

Every unique combination of dimension values is stored once in DimensionAttributeValueCombination (DAVC) and referenced by RecId. The supporting cast:

TableHolds
DimensionAttributeThe dimension definitions (Department, CostCenter…)
DimensionAttributeValueA single value for one attribute (CostCenter "CC010")
DimensionAttributeValueCombinationA full combination (the accounting string)
DimensionAttributeValueSetA set of values for default dimensions
DimensionAttributeLevelValueThe exploded per-attribute rows for a combination

The LedgerDimension and DefaultDimension fields you see on tables are just RecId pointers into this structure. That indirection is why you cannot read a cost center with a simple field access.

Reading dimensions in X++

To get a human-readable value out of a default dimension, walk the value set:

public static str getDimensionValue(
    DimensionDefault _defaultDimension, str _dimensionName)
{
    DimensionAttributeValueSetItem  setItem;
    DimensionAttributeValue         dimValue;
    DimensionAttribute              dimAttr;

    select firstonly Value from setItem
        where setItem.DimensionAttributeValueSet == _defaultDimension
        join RecId from dimValue
            where dimValue.RecId == setItem.DimensionAttributeValue
        join Name from dimAttr
            where dimAttr.RecId == dimValue.DimensionAttribute
               && dimAttr.Name  == _dimensionName;

    return setItem.DisplayValue;
}

Building a default dimension in code

The supported way to create a combination is the DimensionDefaultingService / DimensionAttributeValueSetStorage helper, never hand-inserting into DAVC:

public static DimensionDefault buildDefaultDimension(
    container _attrNames, container _values)
{
    DimensionAttributeValueSetStorage storage =
        new DimensionAttributeValueSetStorage();

    for (int i = 1; i <= conLen(_attrNames); i++)
    {
        DimensionAttribute attr =
            DimensionAttribute::findByName(conPeek(_attrNames, i));

        if (attr.RecId)
        {
            DimensionAttributeValue dimValue =
                DimensionAttributeValue::findByDimensionAttributeAndValue(
                    attr, conPeek(_values, i), false, true);
            storage.addItem(dimValue);
        }
    }
    return storage.save();
}

addItem plus save() deduplicates against existing combinations for you, so you never create orphan rows.

Account structures and advanced rules

What combinations are valid is governed by account structures and advanced rules, configured under General ledger > Chart of accounts > Structures. An account structure says "for main accounts 6000–6999, Department and CostCenter are required, BusinessUnit is blank." Advanced rules layer conditional dimensions on top — "if Department is Sales, also require SalesRegion."

This is why a voucher posting can fail with "the dimension combination is not valid": the values exist, but the account structure does not permit that shape for that main account. When you build ledger dimensions in code, validate against the active structure or the posting will reject them.

Reporting reality

Financial reporting (the Management Reporter successor) and Power BI both read the exploded DimensionAttributeLevelValue rows, which is why dimensions you populate correctly "just appear" in reports. If a dimension shows blank in reports but looks fine on the form, the usual culprit is a default dimension that was never merged into the ledger dimension at posting time — the report reads the voucher, and the voucher never got the tag.

Once you internalize default vs ledger and combinations stored once by RecId, the rest of the model stops being intimidating and starts being predictable.

Keep reading

Newsletter

New posts, straight to your inbox

One email per post. No spam, no tracking pixels, unsubscribe anytime.

Comments

No comments yet. Be the first.