Tuesday, 8 September 2020

Microsoft Dynamics 365: Changes to Financial Dimensions Structure

For example, if you would activate a CustomerSegment dimension, the system would create a new CustomerSegment and CustomerSegmentValue columns in backing SQL table (these fields will not be visible AOT in Visual Studio) for both DimensionAttributeValueSet and DimensionAttributeValueCombination tables.

CUSTOMERSEGMENTCUSTOMERSEGMENTVALUE
5637144684100
5637144684100
56371446804000

Example 1: CustomerSegment and CustomerSegmentValue dimension

In this example, CustomerSegment field is the Record of the dimension’s backing entity (in this case CustTable) and CustomerSegmentValue is CustomerSegment display value.

To select a specific dimension value directly from DimensionAttributeValueCombination (or DimensionAttributeValueSet) table you will need to specify its fieldID instead of its name. For that purpose, you can use the DimensionAttributeValueCombination/getDimensionValueFieldId method.

To see how dimension-based select statements can be shortened in Dynamics 365 let’s look at an example which selects projects that have a specific BusinessUnit dimension display value:

static void CF1_GetProjectsWithCustomerSegment(Args _args)
{
    #define.BUSINESSUNIT('BusinessUnit')

    ProjTable                       projTable;

    DimensionAttribute              da;
    DimensionAttributeValue         dav;
    DimensionAttributeValueSet      davs;
    DimensionAttributeValueSetItem  davsi;

    while select ProjId from projTable
        exists join davs
            where davs.RecId == projTable.DefaultDimension
        exists join davsi
            where davsi.DimensionAttributeValueSet == davs.RecId &&
                  davsi.DisplayValue               == '001'
        exists join dav
            where dav.RecId == davsi.DimensionAttributeValue
        exists join da
            where da.RecId == dav.DimensionAttribute &&
                  da.Name  == #CUSTOMERSEGMENT
    {
        info(projTable.ProjId);
    }
}

Example 2: Financial dimension-based select statements in Dynamics AX 2012

 

class CF1_GetProjectsWithBusinessUnit
{        
    public static void main(Args _args)
    {        
        Const Name BUSINESSUNIT = 'BusinessUnit';

        FieldId                     businessUnitId;
        ProjTable                   projTable;
        DimensionAttributeValueSet  davs;

        businessUnitId = DimensionAttributeValueCombination::getDimensionValueFieldId(BUSINESSUNIT);

        while select ProjId from projTable
            exists join davs
                where davs.RecId          == projTable.DefaultDimension &&
                    davs.(businessUnitId) == '20'
        {
            info(projTable.ProjId);
        }
    }
}

Example 3: Financial dimension-based select statements in Dynamics 365

 

As you can see, select statements are now much shorter, which in turn increases solution performance. The same applies to queries in Dynamics 365.

class CF1_GetProjectsWithBusinessUnit
{        
    public static void main(Args _args)
    {        
        Const Name BUSINESSUNIT = 'BusinessUnit';

        FieldId         businessUnitId;
        ProjTable       projTable;

        businessUnitId = DimensionAttributeValueCombination::getDimensionValueFieldId(BUSINESSUNIT);

        Query                   q;
        QueryRun                qr;
        QueryBuildDataSource    qbdProjTable, qbdDimensionAttributeValueSet;

        q = new Query();

        qbdProjTable = q.addDataSource(tableNum(ProjTable));

        qbdDimensionAttributeValueSet = qbdProjTable.addDataSource(tableNum(DimensionAttributeValueSet));
        qbdDimensionAttributeValueSet.relations(true);

        SysQuery::findOrCreateRange(qbdDimensionAttributeValueSet, businessUnitId).value('20');

        qr = new QueryRun(q);

        while (qr.Next())
        {
            projTable = qr.get(tableNum(ProjTable));
        }
    }
}

Example 4: Financial dimension-based queries in Dynamics 365

Changes to DimensionStorage class in Dynamics 365

In Dynamics 365, quite a few methods from DimensionStorage class (which is commonly used in development projects) were moved to other classes. Here is a list of those methods:

AX 2012 DimensionStorage methodDynamics 365 DimensionStorage method
getMainAccountIdFromLedgerDimensionLedgerDimensionFacade/ getMainAccountRecIdFromLedgerDimension
getMainAccountFromLedgerDimensionLedgerDimensionFacade/getMainAccountFromLedgerDimension
getDefaultAccountLedgerDefaultAccountHelper/getDefaultAccountFromMainAccountRecId
getDefaultAccountForMainAccountNumLedgerDefaultAccountHelper/getDefaultAccountFromMainAccountId
accountNum2LedgerDimension
And getDynamicAccount
LedgerDynamicAccountHelper/getDynamicAccountFromAccountNumber
ledgerDimension2AccountNumLedgerDynamicAccountHelper/ getAccountNumberFromDynamicAccount
getDefaultDimensionFromLedgerDimensionLedgerDimensionFacade/getDefaultDimensionFromLedgerDimension


There may be cases when you need to retrieve a ledger or default dimensions from display values. Doing that has changed a bit in Dynamics 365 and I will give you a few solutions options.

To get the ledger dimension from display values you will need to set each segment that has value using DimensionStorageSegment class and then use the ledger AccountDimensionResolver resolve method to retrieve ledger dimension. Here’s an example of how that could be achieved:

//Dimension segement constant List
const Name MAINACCOUNT     = 'MainAccount';
const Name BUSINESSUNIT    = 'BusinessUnit';
const Name CUSTOMERSEGMENT = 'CustomerSegment';
const Name PROJECT         = 'Project';
const Name PRODUCT         = 'Product';
const Name GROUPCODE       = 'GroupCode';
const Name TARGET          = 'Target';

public DimensionDynamicAccount getLedgerDimensionFromDisplayValues (CF1_DisplayValueTable _displayValueTable) { LedgerAccountDimensionResolver ledgerAccountDimensionResolver; DimensionStorage dimensionStorage; DimensionHierarchy dimensionHierarchy = DimensionHierarchy::find(DimensionHierarchy::getAccountStructure(MainAccount::findByMainAccountId(_displayValueTable.Account).RecId, Ledger::current()));
dimensionStorage = DimensionStorage::construct(); dimensionStorage.addAccountStructure(dimensionHierarchy.RecId); this.setSegments(dimensionStorage, dimensionHierarchy.RecId, _displayValueTable.Account, MAINACCOUNT); this.setSegments(dimensionStorage, dimensionHierarchy.RecId, _displayValueTable.Object1, BUSINESSUNIT); this.setSegments(dimensionStorage, dimensionHierarchy.RecId, _displayValueTable.Object2, CUSTOMERSEGMENT); this.setSegments(dimensionStorage, dimensionHierarchy.RecId, _displayValueTable.Object3, PROJECT); this.setSegments(dimensionStorage, dimensionHierarchy.RecId, _displayValueTable.Object4, PRODUCT); this.setSegments(dimensionStorage, dimensionHierarchy.RecId, _displayValueTable.Object5, GROUPCODE); this.setSegments(dimensionStorage, dimensionHierarchy.RecId, _displayValueTable.Object6, TARGET); ledgerAccountDimensionResolver = LedgerAccountDimensionResolver::newResolver(dimensionStorage.getComboDisplayValue(), dimensionHierarchy.Name); ledgerAccountDimensionResolver.parmDimensionFormat(DimensionHierarchyView::findByHierarchy(dimensionHierarchy.RecId).Segments); return ledgerAccountDimensionResolver.resolve(); } public void setSegments(DimensionStorage _dimensionStorage, DimensionHierarchyId _dimensionHierarchyId, DimensionValue _dimensionValue, Name _dimensionName) { DimensionAttribute dimensionAttribute = DimensionAttribute::findByName(_dimensionName); DimensionAttributeValue dimensionAttributeValue = DimensionAttributeValue::findByDimensionAttributeAndValue(dimensionAttribute, _dimensionValue);
if (_dimensionValue && dimensionAttributeValue && _dimensionHierarchyId && dimensionAttribute) { DimensionStorageSegment dimStorageSegment; dimStorageSegment = DimensionStorageSegment::construct( _dimensionValue, dimensionAttributeValue.RecId, dimensionAttributeValue.HashKey); _dimensionStorage.setSegment(DimensionHierarchyLevel::findByDimensionHierarchyAndDimAttribute(_dimensionHierarchyId, dimensionAttribute.RecId).Level, dimStorageSegment); } }

 Example 5: Financial dimensions from display values in Dynamics 365

To retrieve a default dimension from the dimension segment display values you will need to use DimensionAttributeValueSetStorage class. Here’s an example of how this can be implemented:

public static DimensionDefault getDefaultDimensionFromDisplayValues(CF1_DisplayValueTable  _displayValueTable)
{
    //Dimension segement constant List
    const Name BUSINESSUNIT    = 'BusinessUnit';
    const Name CUSTOMERSEGMENT = 'CustomerSegment';
    const Name PROJECT         = 'Project';
    const Name PRODUCT         = 'Product';
    const Name GROUPCODE       = 'GroupCode';
    const Name TARGET          = 'Target';

    DimensionAttributeValueSetStorage   invoiceDAVSStorage;

    void addItem(DimensionAttributeValue _dav)
    {
        if (_dav)
        invoiceDAVSStorage.addItem(_dav);
    }

    invoiceDAVSStorage = new DimensionAttributeValueSetStorage();

    addItem(DimensionAttributeValue::findByDimensionAttributeAndValue(DimensionAttribute::findByName(BUSINESSUNIT), _displayValueTable.Object1));
    addItem(DimensionAttributeValue::findByDimensionAttributeAndValue(DimensionAttribute::findByName(CUSTOMERSEGMENT), _displayValueTable.Object2));
    addItem(DimensionAttributeValue::findByDimensionAttributeAndValue(DimensionAttribute::findByName(PROJECT), _displayValueTable.Object3));
    addItem(DimensionAttributeValue::findByDimensionAttributeAndValue(DimensionAttribute::findByName(PRODUCT), _displayValueTable.Object4));
    addItem(DimensionAttributeValue::findByDimensionAttributeAndValue(DimensionAttribute::findByName(GROUPCODE), _displayValueTable.Object5));
    addItem(DimensionAttributeValue::findByDimensionAttributeAndValue(DimensionAttribute::findByName(TARGET), _displayValueTable.Object6));

    return invoiceDAVSStorage.save();
}

Figure 6: Retrieve Default Dimension in Dynamics 365

I hope this has given you some insight into the differences between dimension structure in Dynamics 365 for Operations [Enterprise Edition] versus Dynamics AX 2012.  Stay tuned for more information and insights from 1ClickFactory on Dynamics 365 for Operations [Enterprise Edition]!

Common Functions

 


Text Functions


Asc
Returns an Integer value representing the character code corresponding to a character.
=Asc(Fields!Description.Value)

AscW
Returns an Integer value representing the character code corresponding to a character.
=AscW(Fields!Description.Value)

Chr
Returns the character associated with the specified character code.
=Chr(65)

ChrW
Returns the character associated with the specified character code.
=ChrW(241)

Filter
Returns a zero-based array containing a subset of a String array based on specified filter criteria.
=Filter(Parameters!MultivalueParameter.Value, "3", True, CompareMethod.Binary)

Format
Returns a string formatted according to instructions contained in a format String expression.
=Format(Globals!ExecutionTime, "Long Date")

FormatCurrency
Returns an expression formatted as a currency value using the currency symbol defined in the system control panel.
=FormatCurrency(Fields!YearlyIncome.Value,0)

FormatDateTime
Returns a string expression representing a date/time value.
=FormatDateTime(Fields!BirthDate.Value,DateFormat.ShortDate)

FormatNumber
Returns an expression formatted as a number.
=FormatNumber(Fields!Weight.Value,2)

FormatPercent
Returns an expression formatted as a percentage (that is, multiplied by 100) with a trailing % character.
=FormatPercent(Fields!Sales.Value/Sum(Fields!Sales.Value, "DataSet1"),0)

GetChar
Returns a Char value representing the character from the specified index in the supplied string.
=GetChar(Fields!Description.Value, 5)

InStr
Returns an integer specifying the start position of the first occurrence of one string within another.
=InStr(Fields!Description.Value, "car")

InStrRev
Returns the position of the first occurrence of one string within another, starting from the right side of the string.
=InStrRev(Fields!Description.Value, "car")

Join
Returns a string created by joining a number of substrings contained in an array.
=Join(Parameters!MultivalueParameter.Value,",")

LCase
Returns a string or character converted to lowercase.
=LCase(Fields!Description.Value)

Left
Returns a string containing a specified number of characters from the left side of a string.
=Left(Fields!Description.Value,4)

Len
Returns an integer containing either the number of characters in a string or the number of bytes required to store a variable.
=Len(Fields!Description.Value)

LSet
Returns a left-aligned string containing the specified string adjusted to the specified length.
=LSet(Fields!Description.Value,4)

LTrim
Returns a string containing a copy of a specified string with no leading spaces (LTrim), no trailing spaces (RTrim), or no leading or trailing spaces (Trim).
=LTrim(Fields!Description.Value)

Mid
Returns a string containing a specified number of characters from a string.
=Mid(Fields!Description.Value,3,4)

Replace
Returns a string in which a specified substring has been replaced with another substring a specified number of times.
=Replace(Fields!Description.Value,"tube","headlight")

Right
Returns a string containing a specified number of characters from the right side of a string.
=Right(Fields!Description.Value,4)

RSet
Returns a right-aligned string containing the specified string adjusted to the specified length.
=RSet(Fields!Description.Value,4)

RTrim
Returns a string containing a copy of a specified string with no leading spaces (LTrim), no trailing spaces (RTrim), or no leading or trailing spaces (Trim).
=RTrim(Fields!Description.Value)

Space
Returns a string consisting of the specified number of spaces.
=Space(3)

Split
Returns a zero-based, one-dimensional array containing a specified number of substrings.
=Split(Fields!ListWithCommas.Value,",")

StrComp
Returns -1, 0, or 1, based on the result of a string comparison.
=StrComp(Fields!Description.Value,First(Fields!Description.Value))

StrConv
Returns a string converted as specified.
=StrConv(Fields!Description.Value,vbProperCase)

StrDup
Returns a string or object consisting of the specified character repeated the specified number of times.
=StrDup(3,"M")

StrReverse
Returns a string in which the character order of a specified string is reversed.
=StrReverse(Fields!Description.Value)

Trim
Returns a string containing a copy of a specified string with no leading spaces (LTrim), no trailing spaces (RTrim), or no leading or trailing spaces (Trim).
=Trim(Fields!Description.Value)

UCase
Returns a string or character containing the specified string converted to uppercase.
=UCase(Fields!Description.Value)