
Table of Content
In the Acumatica business logic one of the most frequent actions is to obtain a graph cache from the graph's Caches collection for a particular DAC type. However, sometimes things can be not as simple as they look. In cases of DAC hierarchies, a graph may use the same graph cache for both base and derived DAC types. You can also explicitly specify what type of graph cache should be returned for a particular DAC type. The goal of this post is to describe this mechanism.
Cache Mappings
For every DAC that is read from the database and modified by the application logic, Acumatica's PXGraph stores graph cache. This cache has the PXCache<SomeDAC> type, where SomeDAC is a cache item type. It is a type of DAC items stored in cache. You can request the graph cache that corresponds to the DAC with the following code:
var cache = graph.Caches[typeof(APRegister)];PXCache class, which hides the actual cache item type. The actual cache item type may be the same as the type of the DAC used to obtain the cache but it also can be different. This usually happens in DAC hierarchies when base and derived DACs are used together in the same graph. For example, in the APInvoiceEntry graph the application logic uses both base APRegister DAC and derived APInvoice DAC. However, there is only one graph cache PXCache<APInvoice> for both of them.Explicit Cache Mapping Initialization
Let's start with the explicit specification of cache mappings for a graph. This is done by overriding the virtual InitCacheMapping method of the graph. Here is an example from the VendorShipmentEntry graph.
// InitCacheMapping in VendorShipmentEntry
public override void InitCacheMapping(Dictionary<Type, Type> map)
{
base.InitCacheMapping(map);
this.Caches.AddCacheMapping(typeof(INLotSerialStatus), typeof(INLotSerialStatus));
}You can see the following in this code:
- You need to call the base
InitCacheMappingmethod before specifying a custom cache mapping. - You can add cache mappings with the
AddCacheMappingmethod. The first parameter is the DAC type and the second is the cache item type. - There is only one cache mapping added in the example. Most of the DACs, which are used in Acumatica's application logic, do not have inheritors and complex DAC hierarchy. Therefore, cache mappings implicitly created for them by the framework is trivial. The DAC type and the cache item type are the same.
- By specifying cache mapping from
INLotSerialStatustoINLotSerialStatuswe ensure that cache mapping for the DAC is stable and is not affected by new graph views.
You can also use the map parameter to edit the collection of cache mappings. The following code from ARInvoiceEntry demonstrates this approach:
// InitCacheMapping in ARInvoiceEntry
public override void InitCacheMapping(Dictionary<Type, Type> map)
{
base.InitCacheMapping(map);
map.Add(typeof(CT.Contract), typeof(CT.Contract));
}Both examples basically do the same. The map parameter references the internal cache mapping dictionary, which is also modified by the AddCacheMapping method. You can see during the debug that the map parameter gets new items after a call to AddCacheMapping. However, there are particular differences between these approaches, which are the following:
- Using the
mapparameter is more flexible. In addition to adding new code mappings you can delete or modify existing ones. However, it also requires more caution. For example, if you try to callmap.Addto add mappings for the same DAC type in multiple places (such as inInitCacheMappingoverrides in base and derived graphs), you will get the following exception: An item with the same key has already been added. - The
AddCacheMappingmethod is more limiting but also is more safe. It doesn't allow you to modify existing cache mappings and always checks if a mapping for a given DAC type is already present before adding a new cache mapping.
Finally, there is AddCacheMappingsWithInheritance helper method that allows you to map a DAC type and all its base DAC types to itself. This is useful when you have a derived DAC and you want to map both it and all its base types to a single graph cache. Here is an example from the VendorMaint graph:
// InitCacheMapping in VendorMaint
public override void InitCacheMapping(Dictionary<Type, Type> map)
{
base.InitCacheMapping(map);
Caches.AddCacheMappingsWithInheritance(this,typeof(VendorR));
Caches.AddCacheMappingsWithInheritance(this,typeof(CRLocation));
}Caches.AddCacheMappingsWithInheritance(this,typeof(VendorR)) adds three cache mappings. It maps VendorR, Vendor, and BAccount DACs to PXCache<VendorR>. You could have added these cache mappings by hand but you really shouldn't. AddCacheMappingsWithInheritance is more concise. It also isn't affected by the changes in the DAC hierarchy unlike cache mappings added manually for each base type. But most importantly, the AddCacheMappingsWithInheritance helper takes into account multiple platform mechanisms related to graph caches and class inheritance (such as the PXBreakInheritanceAttribute attribute).Implicit Cache Mapping Initialization
InitCacheMapping mechanism. In fact, this is the most frequently used mechanism to initialize cache mappings, which handles almost all of them. I called it the implicit initialization because there is no way to configure the process directly. It takes all graph views and initializes caches and cache mappings for their main DACs (first tables in the BQL query).AddCacheMappingsWithInheritance method described in the previous section to initialize cache mappings of their main DACs. Usually, DACs don't have inheritors. It doesn't matter for them if the AddCacheMappingsWithInheritance method is used. The situation is simple here: If the DAC type is used by the graph's views, one cache is initialized for this DAC type and the cache item type is the same as DAC type.AddCacheMappingsWithInheritance method leads to the dependency on the order in which graph views are processed. Suppose that there are two DACs and one is derived from the other. Let's call these two DACs a base DAC and a derived DAC. What will be the cache mappings for these DACs if the graph contains the following two views: one for the base DAC (PXSelect<BaseDac> baseView) and one for the derived DAC (PXSelect<DerivedDAC> derivedView)?derivedView is processed first, AddCacheMappingsWithInheritance adds cache mappings for its main DAC DerivedDAC and its base type BaseDac to a single PXCache<DerivedDAC> cache. This means that the graph has only one cache for these two DACs. On the other hand, if baseView is processed first, AddCacheMappingsWithInheritance adds cache mapping for its main DAC BaseDac to the PXCache<BaseDac > cache. Later, during the processing of derivedView, AddCacheMappingsWithInheritance will add cache mapping for its main DAC DerivedDAC to the PXCache<DerivedDAC> cache but it won't add cache mapping for its base type BaseDac because the cache mapping for BaseDac was already added before. This means that the graph has two caches for these DACs.partial classes feature is the most notable among these cases. Partial classes can be declared in multiple files and may contain partial methods which also can be declared in two different files. The concept of declaration order does not fit well for them. If your graph is a partial class declared in multiple files, then you should keep in one file all its Acumatica specific type members initialized by the framework (such as views and actions) to avoid issues with the implicit cache mapping initialization.Cache Mappings Rules for DAC Hierarchies
- If the view with the base DAC is declared first in the graph, there are two caches (one for the base DAC and one for the derived DAC). For each DAC, cache mapping points to its own cache with the same cache item type.
- If the view with the derived DAC is declared first in the graph, there is one cache for the derived DAC. For each DAC, cache mapping points to the same cache with the derived item type.
Now let's see how cache mappings work with graph hierarchies.
Graph Hierarchies
In the previous section, we have considered the case when both views are declared in one graph. But what if we add graph inheritance and graph extensions to the picture? The following diagram answers this question. Here we can see the number of caches in the graph for all different configurations of views declared in it, its base graph and its graph extension. The structure of cache mappings is obvious from the number of caches.

- Views from the base graph have lower processing order than views from the derived graph. You can imagine that all views from base and derived graphs are declared inside one graph and views from the base graph are declared first.
- Views from the graph extension have higher processing order than views from the graph. In this aspect graph extensions are similar to derived graphs. You can imagine that all views from the graph and graph extension are declared inside one graph and views from the graph are declared first.
We can describe implicit cache mapping rules like this.
Cache Mappings Rules for DAC and Graph Hierarchies
- For a graph hierarchy imagine all views to be declared in a single combined graph. Views from base graphs have lower declaration order. Views from graph extensions have higher declaration order.
- If the view with the base DAC has lower declaration order than the view with the derived DAC, there are two caches (one for the base DAC and one for the derived DAC). For each DAC, cache mapping points to its own cache with the same cache item type.
- If the view with the derived DAC has lower declaration order than the view with the base DAC, there is one cache for the derived DAC. For each DAC, cache mapping points to the same cache with the derived item type.
Best Practice for Management of Cache Mappings
Before the introduction of the InitCacheMapping mechanism, developers managed the cache mappings creation by trying to indirectly affect the implicit cache mapping initialization. They changed the order of graph views or introduced redundant "dummy" views:
// Dummy graph view
[PXHidden]
public PXSelect<ARTran> _dummyARTran;Such views are not used in code and they specify a trivial BQL query. Their names usually contain word "dummy". The purpose of dummy views is to ensure initialization of cache mappings for the view's main DAC and affect the order in which these mappings are initialized by the implicit mapping initialization during the graph creation.
Although you can see these approaches in the legacy code, we do not recommend that you use them now.
Currently, It is recommended to manage graph cache mappings with an explicit specification of cache mappings in the InitCacheMapping override.