Memoization and it's use in AX365

Let us see the definition of memoization in wiki.

"Memoization or memoisation is an optimization technique used primarily to speed up computer programs by storing the results of expensive function calls and returning the cached result when the same inputs occur again."

Background: In our implementation project we have a dimension helper class which has specific static methods to find the dimension value of a dimension or dimension value description or by dimension attribute recid etc. So in the implementation many customizations or reports or data entity etc makes use of ready made dimension helper class. As a result of which MS detected there was a whooping amount of queries to DIMENSIONATTRIBUTEVALUESETITEM table which resulted to overwhelming of SQL and causing high memory / buffer IO.

So lets see one such method getDimensionDisplayValueByAttributeRecid from the dimension helper class which uses DimensionAttributeValueSetStorage class. We can see the find method in the below screenshot which uses DimensionAttributeValueSetItem and loops records

No alt text provided for this image
No alt text provided for this image

As per MS using DimensionAttributeValueSetStorage class to fetch dislay values is a heavy weight approach. In order to reduce the amount of db calls for same records rather then going sql we would need to cache the records. Standard have already implemented the memoization pattern in many scenarios. eg: LedgerCache, BudgerCache classes.

So now we would improve our getDimensionDisplayValueByAttributeRecid method using memoization pattern.

Create a enum with an element which defines a scope

No alt text provided for this image

Create a class for caching

/// <summary>
///    The <c>KrishDimensionCache</c> class caches default dimension information.
/// </summary>
/// 

public class KrishDimensionCache
{
    protected void new()
    {
    }


    /// <summary>
    ///    Clears the Krish cache for all Krish cache scopes.
    /// </summary>
    public static void clearAllScopes()
    {
        DictEnum dictEnum = new DictEnum(enumnum(KrishDimensionCacheScope));
        int i;


        for (i = 0; i <= dictEnum.values(); i++)
        {
            KrishDimensionCache::clearScope(dictEnum.index2Value(i));
        }
    }


    /// <summary>
    ///    Clears the Krish cache for the specified Krish cache scope.
    /// </summary>
    /// <param name="_scope">
    ///    The Krish cache scope for which to clear.
    /// </param>
    public static void clearScope(KrishDimensionCacheScope _scope)
    {
        SysGlobalObjectCache objectCache = ClassFactory.globalObjectCache();


        objectCache.clear(KrishDimensionCache::getCacheScopeStr(_scope));
    }


    /// <summary>
    ///    Gets the Krish cache scope identifier for the specified Krish cache scope.
    /// </summary>
    /// <param name="_scope">
    ///    The Krish cache scope for which to get the identifier.
    /// </param>
    /// <returns>
    ///    The identifier of the Krish cache scope.
    /// </returns>
    private static str getCacheScopeStr(KrishDimensionCacheScope _scope)
    {
        // Int2Str is used instead of Enum2Str to get the int
        // value like '2' instead of the string value for
        // the scope to ensure uniqueness. The _scope enum
        // is implicitly cast to an int by this call. This
        // avoids calling strfmt() which causes a kernel
        // callback running under IL and is therefore significantly
        // slower.
        return 'KrishDimensionCache_' + int2str(_scope);
    }


    /// <summary>
    ///    Gets the Krish cache value for the specified Krish cache scope and value key.
    /// </summary>
    /// <param name="_scope">
    ///    The Krish cache scope for which to obtain the cache value.
    /// </param>
    /// <param name="_key">
    ///    The value key for which to obtain the cache value.
    /// </param>
    /// <returns>
    ///    The Krish cache value.
    /// </returns>
    public static container getValue(KrishDimensionCacheScope _scope, container _key)
    {
        SysGlobalObjectCache objectCache = ClassFactory.globalObjectCache();


        return objectCache.find(KrishDimensionCache::getCacheScopeStr(_scope), _key);
    }


    /// <summary>
    ///    Inserts the Krish cache value.
    /// </summary>
    /// <param name="_scope">
    ///    The Krish cache scope to insert.
    /// </param>
    /// <param name="_key">
    ///    The value key to insert.
    /// </param>
    /// <param name="_value">
    ///    The value to insert.
    /// </param>
    public static void insertValue(KrishDimensionCacheScope _scope, container _key, container _value)
    {
        SysGlobalObjectCache objectCache = ClassFactory.globalObjectCache();


        objectCache.insert(KrishDimensionCache::getCacheScopeStr(_scope), _key, _value);
    }


    /// <summary>
    ///    Indicates whether the specified value key in the Krish cache exists.
    /// </summary>
    /// <param name="_scope">
    ///    The Krish cache scope of the Krish cache to check for existence.
    /// </param>
    /// <param name="_key">
    ///    The value key of the Krish cache to check for existence.
    /// </param>
    /// <returns>
    ///    true if the specified value key exists; otherwise, false.
    /// </returns>
    public static boolean valueExists(KrishDimensionCacheScope _scope, container _key)
    {
        SysGlobalObjectCache objectCache = ClassFactory.globalObjectCache();


        return (objectCache.find(KrishDimensionCache::getCacheScopeStr(_scope), _key) != connull());
    }


}

Now we will use this KrishDimensionCache class in our dimensionHelperclass method getDimensionDisplayValueByAttributeRecid to implement memoization.

No alt text provided for this image

In order to use such pattern only thing we need to identify is the key , value combination.

Oleksandr D.

Dynamics 365 F&SCM Integration Developer/Technical Architect

2 年

AFAIK this is named idempotence

回复
Tanuj Shah

Technical Architect at Hitachi Solutions | D365FinOps | Azure | LCS | Power platform | SnapLogic | MCP

4 年

Interesting ! Will try this out for sure.

Vishwas Bhat

Dynamics 365 Finance and Operations

4 年

Good one

Ganesan Natarajan

Dynamics 365 Finance and SCM Technical Consultant

4 年

Very useful post. Thanks for sharing.

Romain Lasmi

Consultant Cloud

4 年

Erdogan ??

回复

要查看或添加评论,请登录

Krishnendu Sinha的更多文章

  • Form Filter limitation in AX 365

    Form Filter limitation in AX 365

    Recently we stumbled upon an issue where the end user was trying to filter records using the "IS ONE OF" criteria to…

    15 条评论
  • If value exists check

    If value exists check

    This check could look very trivial but it could definitely help us save from getting future bugs. If we are writing any…

    4 条评论
  • Job History cleanup -Data Management

    Job History cleanup -Data Management

    Recently we faced an issue where the data comes through Recurring Integration and some of the data din't make it to…

    1 条评论
  • Post data to D365F&O with OData using Postman tool

    Post data to D365F&O with OData using Postman tool

    In this post we will see how to POST data using Odata to D365 and few caveats. Before proceeding please go through…

    4 条评论
  • Customize Open in Excel Menu in D365F&O

    Customize Open in Excel Menu in D365F&O

    We are cognizant that the open in office (open in excel ) menu brings the data entities based on the root data source…

    4 条评论
  • Use Postman tool in D365 without registering in Azure ADD for testing Data Entities(Odata) Rest APIs.

    Use Postman tool in D365 without registering in Azure ADD for testing Data Entities(Odata) Rest APIs.

    Postman is handy tool when you want to test restful API’s. You can simply create entity in D365 finance and operations,…

社区洞察

其他会员也浏览了