Have you ever noticed how Dynamics 365 for Finance and Operations (D365FO) initially loads at a snail's pace 🐌 but then speeds up 🚀 when you revisit the same form? That's the magic of caching at work! Caching boosts your application's speed and performance by storing frequently used data in cache memory, letting you skip those time-consuming database calls or complex code runs. 🕒➡️🕐
In this article, we'll take a deep dive into two powerful classes for caching data in D365FO and describe with examples what is the difference between SysGlobalCache and SysGlobalObjectCache. Whether you're wrestling with global variables due to less-than-ideal design patterns 🤔 or striving for peak efficiency 🏆, these cache classes have got you covered. Buckle up and let's get started! 🛠️
When should I use caching in D365FO?
Caching is definitely an abstract concept and it's sometimes hard to imagine where you should use it in D365FO. In real-world scenarios, caching can be used for:
- Commonly accessed master data like customer, product, and price lists benefit from caching to speed up transaction processing and queries.
- Session-specific information or so-called session data, such as user preferences or recently viewed items, to improve user experience without frequent database queries.
- Cache configurations that are fetched frequently but rarely change.
- Cache common record buffers.
- Security-related metadata to swiftly verify user roles and permissions during the session.
- When implementing batch jobs (e.g., Batch Processing) that read the same dataset multiple times, caching can drastically reduce I/O operations against the database.
- Forms that display summarized or calculated data should use caching to avoid re-calculating the same data repeatedly.
- Integrations with external systems when calling API can minimize the frequency of requests such as authorization, authentications and others.
- Commonly used report data or query results to improve the performance of reporting tools and dashboards.
How and when to use SysGlobalCache?
The SysGlobalCache class allows you to store and retrieve values from a global in-memory cache, available for the entire client session scope.
In D365FO you can find the following 3 instances of the SysGlobalCache that can be accessed via:
- appl variable that references a pre-instantiated object from the Application class,
- classFactory variable that references object from the ClassFactory class,
- infolog variable that references object from the Info class.
In the following example, let’s see how to use SysGlobalCache by using appl global variable. To get the global cache variable located on the Application class, use the following code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
str owner = 'Docentric'; // Identifies the unique value of the owner (e.g., use className here) str key = 'Sum'; // The key is used to access the value (e.g., use object or property unique name) int value = 1 + 2; // The calculation result or object of anytype boolean isVolatile = true; // The key-value pair can be cleared from cache // Get the instance of the Global Cache SysGlobalCache globalCache = appl.globalCache(); // Set the value of your global cache // The last method parameter isVolatile is optional and it's by default true, which means that the key-value pair can be cleared from the cache globalCache.set(owner, key, value, isVolatile); // Check if a key is already set in the cache if (appl.globalCache().isSet(owner, key) == true) { // Get the value from the SysGlobalCache value from anywhere in the same client session scope int value = appl.globalCache().get(owner, key); } // Removes the value stored for a specified owner under a specified key boolean isKeyValuePairCleared = appl.globalCache().remove(owner, key); // Clears all the values stored for a given owner boolean isOwnerCacheCleared = appl.globalCache().clear(owner); // Removes all volatile values from the cache appl.globalCache().flush(); |
The same applies to instances of the ClassFactory and Info classes. There are many places in the standard code where the SysGlobalCache is used, and you can find them with the Find All References option in Visual studio.
You can also create your own instance of the SysGlobalCache class in your code using the constructor static method SysGlobalCache::construct().
In the next example, we will show how SysGlobalCache is utilized in Docentric AX Free Edition for the All attachments form. This form displays all D365FO attachments in a grid, allowing a user with appropriate permissions to view, open, edit and download any attachment.
After the user selects a record in the grid, we must verify whether the user has permissions to view or edit the associated attachment details, based on the security context of the DocuView form. To accomplish this, we implement a helper method that takes the selected DocuRef buffer as an input parameter. The helper method code looks like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 |
public static container allowViewOrEditDocuRef(DocuRef _docuRef) { boolean allowView = false; boolean allowEdit = false; // Set a global cache that stores the AllowView and AllowEdit flags for each selected DocuRef record. // Use SGC and not SGOC because we want the scope to be in the current user session. The cache is cleared in the close method of the DocAllAttachments form. // Owner - DocAllAttachments, Key - DocuRef.RecId, Value - [AllowView (true/false), AllowEdit (true/false)]. str owner = formStr(DocAllAttachments); SysGlobalCache globalCache = ClassFactory.globalCache(); container conAllowOrEditDocuRef = globalCache.get(owner, _docuRef.RecId, conNull()); if (conAllowOrEditDocuRef != conNull()) { return conAllowOrEditDocuRef; } int infoLine; try { infoLine = infologLine(); // We need to close the existing instance of the DocuView form before we can create a new one if (Docu::Instance() && Docu::Instance().docuView()) { Docu::Instance().clearDocuView(); } Args args = new Args(); args.lookupRecord(_docuRef); FormRun formRun = new MenuFunction(menuItemDisplayStr(DocuView), MenuItemType::Display).create(args); if (formRun) { allowView = true; allowEdit = formRun.getDocuRefAllowEdit_DC(); formRun.close(); formRun = null; } } catch { infolog.cut(infoLine + 1, infologLine()); } conAllowOrEditDocuRef = [allowView, allowEdit]; globalCache.set(owner, _docuRef.RecId, conAllowOrEditDocuRef); return conAllowOrEditDocuRef; } |
At the start of the method, we first query the global cache to see if it already exists. If it does, we return a container populated with the AllowView and AllowEdit flags. If not, we create a new DocuView form to evaluate the user's permissions for viewing and editing the attachment details. After this evaluation, we cache these permissions in the global cache for faster access in subsequent user sessions.
How and when to use SysGlobalObjectCache?
The SysGlobalObjectCache (SGOC) class serves as a kernel-managed caching mechanism, superseding the older SysObjectCache. Introduced in AX2012, SGOC's primary function is to facilitate data sharing across all user sessions in a single process (e.g., single AOS node). In other words, data cached from one user connection becomes accessible to all other users. SGOC operates by storing key-value pairs within a defined "scope".
Both the key and value in SGOC are containers. The key container uniquely identifies the cached object, while the value container holds the object's data.
To access SGOC, you can utilize the global classFactory variable, as the SGOC instance resides on the ClassFactory system class:
1 |
SysGlobalObjectCache sgoc = classFactory.globalObjectCache(); |
Being a singleton class, only one SysGlobalObjectCache instance exists system-wide. However, you can still instantiate a new SGOC object:
1 |
SysGlobalObjectCache sgoc = new SysGlobalObjectCache(); |
If an instance already exists, any new instance will refer to the first original object (i.e., singleton instance). Thus, a value set via one instance is readily available to all other references of SGOC.
The following example show you how to use the SGOC:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
GlobalObjectCacheScope scope = 'Docentric'; // Identifies the unique value of the owner (e.g., use className here) // All the key-value pair are containers container key = ['Sum']; // The key is used to access the value (e.g., use object or property unique name) container value = [1 + 2]; // The calculation result or object of anytype // Get the instance of the SysGlobalObjectCache SysGlobalObjectCache sgoc = new SysGlobalObjectCache(); // Insert a scoped key-value pair sgoc.insert(scope, key, value); // Print to Infolog all the keys stored in the SysGlobalObjectCache sgoc.print(scope); // Get the value of the scoped key-value pair container sumResult = sgoc.find(scope, key); // If the key exist if (sumResult != conNull()) { // Remove only the scoped key-value pair sgoc.remove(scope, key); } // Clear all the key-value pairs in the requested scope sgoc.clear(scope); // Clear all key-value pairs in all scopes SysGlobalObjectCache::clearAllCaches(); |
To learn more about SysGlobalObjectCache, check the following Microsoft documentation.
There are multiple areas where the SGOC is used in D365FO, like for Budget cache, Dimension cache, Price cache and so on.
Now let's look at one of the examples how we used SysGlobalObjectCache. We used SGOC in the development of a feature that saves reports to SharePoint with metadata fields. Since metadata insert/update operations occur in isolated sessions separate from the user session, SGOC is essential for relaying accurate error messages to the user should metadata updates fail. The following example is part of DocDocuActionFileWithMetadata used to store document to attachements or print archive.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
public boolean attachFile(DocuRef _ref, str _fileName, str _fileContentType, System.IO.Stream _fileContents) { // ... only a snippet from the method // If the metadata is not updated successfully and the uploadFileAllowedWithoutMetadata is set to true, // the error message must be stored in the global cache, as this is the only way to display the correct // error message to the user. if (result && errorMsg) { SysGlobalObjectCache globalCache = new SysGlobalObjectCache(); // _ref represents DocuRef buffer and the errorMsg represents the error message we want to show to the user later on globalCache.insert(identifierStr(DocDocuActionFileWithMetadata), [_ref.RecId], [errorMsg]); } } |
To retrieve the message from SGOC and display it to the user, we used the following piece of code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
public void add(DocuRef _docuRef, Filename _filename = '') { // ... only a snippet from the method // If the metadata was not successfully updated when the file was attached and the uploadFileAllowedWithoutMetadata flag // is set to true, the error message was stored in the global cache, as this is the only way to get the correct error message // and display it to the user. if (docuType.FilePlace == DocuFilePlace::SharePoint) { SysGlobalObjectCache globalCache = new SysGlobalObjectCache(); container cntErrorMsg = globalCache.find(identifierStr(DocDocuActionFileWithMetadata), [_docuRef.RecId]); globalCache.remove(identifierStr(DocDocuActionFileWithMetadata), [_docuRef.RecId]); if (cntErrorMsg != conNull()) { DocGlobalHelper::handleWarning(conPeek(cntErrorMsg, 1), false, funcName(), false); } } } |
Troubleshooting and clearing the SysGlobalObjectCache
In rare cases, D365FO may over-cache data, resulting in a failure to refresh the cache. This can create operational issues. To resolve this, one approach is to utilize the SysFlushAOD action menu item. Although this menu item is not directly accessible via any form, it can be invoked through the following URL:
https://[your environment hostname]/?mi=SysClassRunner&cls=SysFlushAOD
This URL triggers the main() method of the SysFlushAOD class. Below is the code snippet from D365FO that executes this action:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
public static void main(Args args) { SysFlushAOD::doFlush(); SysFlushAOD::clearManagedCaches(); SysFlushAOD::clearGlobalObjectCaches(); SysEvent::fireEvent(SysEventType::FlushAOD); SysExtensionCache::clearAllScopesInternal(); SysFlushAOD::clearGlobalCaches(); if (args && args.parmEnum() == NoYes::No) { info("@SYS68564"); } } |
Wrapping It Up: To Cache or Not to Cache 🤔
And there you have it, folks! When it comes to juggling data in D365FO, you've got two juggernauts 🤹♂️: SysGlobalCache and SysGlobalObjectCache.
Remember, SysGlobalCache is your go-to buddy for session-specific needs. On the flip side, if you're looking to share data across multiple sessions, SysGlobalObjectCache is your cross-session hero.
So before you decide, take a moment to ponder 🤔: "Is this data just for me, or is it a party everyone's invited to?". Your answer will guide you to the right caching mechanism!
Happy coding, cache wizards! 🧙♂️💻