Skip to main content

Mapping DAC Type to PXCache Item Type in PXGraph

  • February 1, 2026
  • 0 replies
  • 8 views

snikomarov36
Acumatica Employee
Forum|alt.badge.img

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)];
Notice that the type of cache here will be a base 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.
 
The graph maps DAC types to graph caches. Such mapping between DAC type and its corresponding graph cache is called cache mapping. Cache mappings can be implicitly calculated by Acumatica Framework during the graph initialization. Depending on the declaration order of graph views in the graph the framework may decide to use only one cache for base and derived DACs. You can also specify cache mappings explicitly.
 
Cache mappings are very important for the graph because they define the set of caches that graph initializes and uses during the DAC record processing.
 

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 InitCacheMapping method before specifying a custom cache mapping.
  • You can add cache mappings with the AddCacheMapping method. 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 INLotSerialStatus to INLotSerialStatus we 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 map parameter 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 call map.Add to add mappings for the same DAC type in multiple places (such as in InitCacheMapping overrides in base and derived graphs), you will get the following exception: An item with the same key has already been added.
  • The AddCacheMapping method 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));
}
Here 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. 
 
Using 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

The initialization of graph caches and cache mappings during the graph creation existed long before the introduction of the 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).
 
Views use the 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.
 
However, when we are dealing with DAC class hierarchies, usage of the 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)?
 
The answer is it depends. If 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.
 
As you can see the order in which graph views are processed is very important. Currently, the views are processed in the order they are returned by .Net Reflection. Although it is not guaranteed explicitly, almost always it is the declaration order.
 
There could be some subtle cases in which the order is undefined. The C# 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.
 
Let's summarize.
 

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.

 

From the diagram above we can see a few things:
  • 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.