Skip to main content
2024-R1Platform

Platform API: Support for a Strongly Typed Facade for the PXCache Object

Last updated: 31 October 2025

Platform API: Support for a Strongly Typed Facade for the PXCache Object

Platform API: Support for a Strongly Typed Facade for the PXCache Object

In previous versions of MYOB Acumatica, a developer would rely on the weakly typed PXCache object to access the cache of modified data records relating to a particular table.

In MYOB Acumatica 2024.1.1, a number of strongly typed overloads have been introduced for the PXCache object. These overloads have return values and input parameters of the TNode type. The type parameter TNode is set to a data access class (DAC) that represents the table whose modified data records are stored in the cache object. We recommend that developers use the PXCache object instead of the simple PXCache object wherever possible.

Use of the PXCache Object

The PXCache object can be declared and initialized as shown in the following code example.

PXCache<SOOrder> orderCache = graph.Caches<SOOrder>();

A developer can also directly cast a PXCache object to PXCache when they are certain that they are working with the correct type. Direct casting of an existing PXCache object can be implemented as shown in the following code example.

public SelectFrom<SOOrder>.Where<...>.View Document;
  
public virtual void Foo()
{
    var orderCache = (PXCache<SOOrder>)Document.Cache;
    ...
}
  
protected virtual void _(Events.RowUpdated<SOOrder> e)
{
    var orderCache = (PXCache<SOOrder>)e.Cache;
    ...
}

Pitfalls of the PXCache Object

A developer should not use the strongly typed PXCache object in the following scenarios:

  • When the DAC that is specified as the TNode parameter of the PXCache object is a mapped DAC—that is, a DAC with PXMappedCacheExtension descendants. These DACs are processed not by the PXCache object, but by the PXModelExtension object.
  • When the DAC that is specified as the TNode parameter of the PXCache object is a candidate for cache substitution. This usually happens in cases of DAC inheritance where the PXTableAttribute attribute is declared on this DAC. In these cases, the developer may get an InvalidCastException exception. The following code example shows such a case.
    public class MyGraph : PXGraph<MyGraph>
    {
        public SelectFrom<ARRegister>.View ARDocuments;
      
        public void Foo()
        {
            PXCache<ARInvoice> cache = this.Caches<ARInvoice>();
        }
    }
      
    public class GraphHelper
    {
        /* Method to enumerate strongly typed cache objects.
           This method is provided by the Acumatica Framework.
        */        
        public static PXCache<T> Caches<T>(this PXGraph graph)
            where T : class, IBqlTable, new()
        {
            return (PXCache<T>)graph.Caches[typeof(T)];
        }
    }
    

    The PXCache<ARInvoice> cache = this.Caches<ARInvoice>(); line in the code above results in an InvalidCastException exception because ARInvoice is a descendant of ARRegister and is marked with the PXTableAttribute attribute. Hence, it is a candidate for cache substitution. Since a cache of ARRegister type is already created by the ARDocuments view in the code above, the graph.Caches[typeof(ARInvoice)]indexer will return an instance of the PXCache<ARRegister> type. Then the system will try to downcast it to the PXCache<ARInvoice> type to match the return type of the graph.Caches<ARInvoice>() method. The PXCache object is not an interface or delegate; thus, it cannot be a covariant or contravariant to its type parameter. Hence, the cast in the code above is not allowed and throws an exception.

Strongly Typed Overloads for CRUD Operations of the PXCache Object

All of the basic methods of the PXCache object—such as Insert(), Update(), and Delete()—now accept a strongly typed parameter of the TNode type, and return a strongly typed object, if applicable. The following table lists these methods for both the strongly and weakly typed versions of the PXCache object.

PXCache

PXCache

void Remove(TNode row)

void Remove(object row)

TNode Update(TNode row)

object Update(object row)

TNode Locate(TNode row)

object Locate(object row)

TNode Insert(TNode row)

object Insert(object row)

TNode GetOriginal(TNode row)

object GetOriginal(object row)

TNode Delete(TNode row)

object Delete(object row)

Note that the weakly typed methods are still available in the PXCache object. A developer can access them by passing a reference of object type. The following code shown an example.

PXCache<SOOrder> orderCache = graph.Caches<SOOrder>();
SOOrder order = new();
// The weakly typed Insert method is called in the following line
object orderObj = orderCache.Insert((object)order); 

Warning: Developers should avoid the usage of the weakly typed method (and other similar weakly typed methods) that is illustrated in the code above. They should watch out for cases where a strongly typed cache's method with a strongly typed row leads to a call to a weakly typed method. In such cases, a call to a weakly typed method indicates that there is a semantic issue in the code because the compiler was unable to implicitly cast the row to the target type of the cache, which is strongly typed.

Strongly Typed Overloads for the Entity Inspection Methods

The strongly typed overloads of the various entity inspection methods are available for the PXCache object. Currently, these overloads do not provide any specific advantages over their weakly typed versions. The following table lists these methods for both the strongly and weakly typed versions of the PXCache object.

PXCache

PXCache

PXEntryStatus GetStatus(TNode row)

PXEntryStatus GetStatus(object row)

bool IsArchived(TNode row)

bool IsArchived(object row)

bool IsKeyFilled(TNode row)

bool IsKeyFilled(object row)

bool ObjectEqual(TNode a, TNode b)

bool ObjectEqual(object a, object b)

Note: There is only one strongly typed overload of the ObjectEqual method because this is the only overload that compares two instances by using all of their fields. When comparing two strongly typed instances by a set of their fields, a developer should use direct equality comparisons by using the == / != operators instead of calling the ObjectEqual<Field1, Field2, ..., FieldN>(object a, object b) method.

Rows Property of the PXCache Object

It is not possible to implement strongly typed overloads for properties and methods of the cache that are used for enumeration or for accessing rows. This is because they do not have any parameters, while overloading can be implemented only based on input parameters. To give developers the ability to use these properties and methods in a strongly typed manner, MYOB Acumatica 2024.1.1 has introduced the new read-only Rows property for the PXCache object. This property returns an instance of the PXCache.IStronglyTypedRows interface, which is implemented by the PXCache<TNode> object itself and hence prevents any additional memory allocation. The following table lists these properties and methods for both the strongly typed version and the weakly typed version of the PXCache object.

PXCache.Rows

PXCache

TNode Current { get; set; }

object Current { get; set; }

TNode ActiveRow { get; set; }

object ActiveRow { get; set; }

TNode Insert()

object Insert()

TNode CreateInstance()

object CreateInstance()

IEnumerable Inserted { get; }

IEnumerable Inserted { get; }

IEnumerable Updated { get; }

IEnumerable Updated { get; }

IEnumerable Deleted { get; }

IEnumerable Deleted { get; }

IEnumerable Cached { get; }

IEnumerable Cached { get; }

IEnumerable Dirty { get; }

IEnumerable Dirty { get; }

IEnumerable Held { get; }

Suppose that a developer declares and initializes a PXCache object by using PXCache<MyEntity> cache = graph.Caches<MyEntity>();. The developer declares a PXCache object by using PXCache cache = graph.Caches[typeof(MyEntity)];

The following table provides some usage examples for some common scenarios that use the properties and methods described in the previous table, with its columns providing the following information:

  • The first column describes the scenario—what the developer wants to do.
  • The second column shows a code example for how to implement the scenario by using the Rows property of the strongly typed PXCache object.
  • The third column shows a code example for how to implement the scenario by using the weakly typed PXCache object.

Usage Scenario

PXCache.Rows

PXCache

Accessing a field of the current instance

if (cache.Rows.Current
.SomeFlag == true)
{
    ...
}
if (((MyEntity)cache.Current)
.SomeFlag == true)
{
    ...
}

Iterating through updated entities

foreach (var ent in 
cache.Rows.Updated)
{
    ent.SomeField = 42;
}
foreach (var ent in 
cache.Updated.Cast<MyEntity>())
{
    ent.SomeField = 42;
}

Creating a new instance of a PXCache item

MyEntity ent = cache.Rows.CreateInstance();

MyEntity ent = (MyEntity)cache.CreateInstance();

The PXCache object already has static strongly typed versions of a number of methods, such as TNode CreateCopy(TNode item). The methods were also declared in the PXCache.IStronglyTypedRows interface as instance methods so that signature collisions for the methods could be avoided. The following table lists these instance methods of the PXCache.IStronglyTypedRows interface.

PXCache.Rows Instance Methods

PXCache Instance Methods

PXCache Static Methods

TNode CreateCopy(TNode item)

object CreateCopy(object item)

TNode CreateCopy(TNode item)

TNode RestoreCopy(TNode item, TNode copy)

void RestoreCopy(object item, object copy)

TNode RestoreCopy(TNode item, TNode copy)

TExtension GetExtension(TNode row)

TExtension GetExtension(object row)

TExtension GetExtension(TNode row)

TNode GetMain(TExtension extension)

object GetMain(TExtension extension)

TNode GetMain(TExtension extension)

TNode Extend(TParent item)

object Extend(TParent item)

TNode Extend(TParent item)

Strongly Typed Overloads for the Data Manipulation Methods

For the data manipulation methods of the strongly typed PXCache object, a developer can use TNode to build a lambda expression that provides the system with a field of this exact entity, such as row => row.Field. This allows the system to infer both the name of the field and its type and hence makes it possible for a developer to set or get strongly typed values with such methods. The following table lists these methods for the strongly and weakly typed versions of the PXCache object.

PXCache

PXCache

void SetValueExt(LambdaExpression fieldSelector, TNode data, TValue value)

void SetValueExt(object data, string fieldName, object value)

void SetValueExt(object data, object value)

void SetDefaultExt(LambdaExpression fieldSelector, TNode data, TValue value = null)

void SetDefaultExt(object data, string fieldName, object value = null)

void SetDefaultExt(object data, object value = null)

void SetValuePending(LambdaExpression fieldSelector, TNode data, TValue value)

void SetValuePending(object data, string fieldName, object value)

void SetValuePending(object data, object value)

TValue GetValuePending(LambdaExpression fieldSelector, TNode data)

object GetValuePending(object data, string fieldName)

object GetValuePending(object data)

TValue GetValueOriginal(LambdaExpression fieldSelector, TNode data)

object GetValueOriginal(object data, string fieldName)

object GetValueOriginal(object data)

Note: The strongly typed API does not have regular GetValue/SetValue methods because there is no difference between these methods and directly accessing a strongly typed instance's properties. Developers should directly access a strongly typed instance's properties.

Suppose that a developer declares and initializes a PXCache object by using the following code.

PXCache<MyEntity> cache = graph.Caches<MyEntity>();
MyEntity row = new();

Suppose that a developer declares and initializes a PXCache object by using the following code.

PXCache cache = graph.Caches[typeof(MyEntity)];
MyEntity row = new();

The following table provides some usage examples for some common scenarios in which the developer uses the data manipulation methods described in the previous table, with the following information provided in the table's columns:

  • The first column describes the scenario (that is, the task the developer is performing).
  • The second column shows a code example for how to implement the scenario by using the strongly typed overload of a data manipulation method.
  • The third column shows a code example for how to implement the scenario by using the weakly typed version of a data manipulation method.

Usage Scenario

PXCache

PXCache

Getting the original value of a particular field

int? value = cache.GetValueOriginal(r => r.MyIntField, row);

int? value = (int?)cache.GetValueOriginal<MyEntity.myIntField>(row);

Requesting defaulting of a field

cache.SetDefaultExt(r =>r.MyIntField, row);

cache.SetDefaultExt<MyEntity.myIntField>(row);

Setting an extension field to a value with event raising

cache.SetValueExt((MyExt e) => e.MyIntField, row, 42);

cache.SetValueExt<MyExt.myIntField>(row, 42);

In the last scenario in the table above for the PXCache object, note that if instead of the integer value of 42, the developer passes a value of a different type, the developer will get a compilation error. However, for the same scenario but the PXCache object, the developer will not get a compilation error if they passed in a value of a different type; instead, a run-time exception will occur.

Note: To access the fields of a cache extension, a developer must explicitly specify the input type of the lambda argument with the type of that cache extension. The default input type is TNode. The method's signature guarantees that only TNode and the extensions compatible with it can be used as the input type of the lambda argument.

Strongly Typed Overloads for the Event-Raising Methods

The following table lists the row-level event-raising methods for the strongly and weakly typed versions of the PXCache object.

PXCache

PXCache

void RaiseRowSelected(TNode item)

void RaiseRowSelected(object item)

bool RaiseRowInserting(TNode item)

bool RaiseRowInserting(object item)

void RaiseRowInserted(TNode item)

void RaiseRowInserted(object item)

bool RaiseRowUpdating(TNode item, TNode newItem)

bool RaiseRowUpdating(object item, object newItem)

void RaiseRowUpdated(TNode newItem, TNode oldItem)

void RaiseRowUpdated(object newItem, object oldItem)

bool RaiseRowDeleting(TNode item)

bool RaiseRowDeleting(object item)

void RaiseRowDeleted(TNode item)

void RaiseRowDeleted(object item)

bool RaiseRowPersisting(TNode item, PXDBOperation operation)

bool RaiseRowPersisting(object item, PXDBOperation operation)

void RaiseRowPersisted(TNode item, PXDBOperation operation, ...)

void RaiseRowPersisted(object item, PXDBOperation operation, ...)

The following table lists the field-level event-raising methods for the strongly and weakly typed versions of the PXCache object. These methods also use a strongly typed lambda expression to select a field, as the data manipulation methods do. The type of the selected field is propagated to the other parameters of these methods.

PXCache

PXCache

bool RaiseCommandPreparing(LambdaExpression fieldSelector, TNode row, TValue? value, ...)

bool RaiseCommandPreparing(string name, object row, object value, ...)

bool RaiseCommandPreparing(object row, object value, ...)

bool RaiseFieldDefaulting(LambdaExpression fieldSelector, TNode row, out TValue newValue)

bool RaiseFieldDefaulting(string name, object row, out object newValue)

bool RaiseFieldDefaulting(object row, out object newValue)

bool RaiseFieldUpdating(LambdaExpression fieldSelector, TNode row, ref TValue newValue)

bool RaiseFieldUpdating(string name, object row, ref object newValue)

bool RaiseFieldUpdating(object row, ref object newValue)

bool RaiseFieldVerifying(LambdaExpression fieldSelector, TNode row, ref TValue newValue)

bool RaiseFieldVerifying(string name, object row, ref object newValue)

bool RaiseFieldVerifying(object row, ref object newValue)

void RaiseFieldUpdated(LambdaExpression fieldSelector, TNode row, TValue oldValue)

void RaiseFieldUpdated(string name, object row, object oldValue)

void RaiseFieldUpdated(string name, object row, object oldValue)

bool RaiseExceptionHandling(LambdaExpression fieldSelector, TNode row, TValue newValue, Exception exception)

bool RaiseExceptionHandling(string name, object row, object newValue, Exception exception)

bool RaiseExceptionHandling(object row, object newValue, Exception exception)

Suppose that a developer declares and initializes a PXCache object by using the following code.

PXCache<MyEntity> cache = graph.Caches<MyEntity>();
MyEntity row = new();

Suppose that a developer declares and initializes a PXCache object by using the following code.

PXCache cache = graph.Caches[typeof(MyEntity)];
MyEntity row = new();

The following table provides some usage examples for some common scenarios that use the event-raising methods described in the previous tables. The columns show the following information:

  • The first column describes the scenario—what the developer wants to do.
  • The second column shows a code example for how the developer can implement the scenario by using the strongly typed overload of an event-raising method.
  • The third column shows a code example for how the developer can implement the scenario by using the weakly typed version of an event-raising method.

Usage Scenario

PXCache

PXCache

Requesting defaulting of own field

cache.RaiseFieldDefaulting(r => r.MyDecimalField, row, out decimal? newValue);

cache.RaiseFieldDefaulting<MyEntity.myDecimalField>(row, out object newValue);

Requesting exception handling of an extension field

cache.RaiseExceptionHandling((MyExt e) => e.MyStringField, row, "foo", new Exception());

cache.RaiseExceptionHandling<MyExt.myStringField>(row, "foo", new Exception());

In the last scenario in the table above, for the PXCache object, note that if a developer passes a value of a different type instead of the foo string value, the developer will get a compilation error. However, for the same scenario but for the PXCache object, a developer would not get a compilation error if they passed in a value of a different type; instead, a run-time exception would occur.

Note: To access the fields of a cache extension, a developer must explicitly specify the input type of the lambda argument with the type of that cache extension. The default input type is TNode. The method's signature guarantees that only TNode and the extensions compatible with it can be used as the input type of the lambda argument.

Attribute-Accessing Method of the PXCache Object

Only one strongly typed method can be used to access the attributes attached to a field for the PXCache object: the GetAttributesOf(LambdaExpression fieldSelector, TNode data = null, bool readOnly = false) method.

As with the types of methods discussed in the previous sections, this method requires a lambda expression as its first parameter, that will provide it with a field whose attributes are to be accessed. The other two parameters of the method are optional. By default, the method does not require a data row and returns the cache-level attributes. However, if a developer passes a data row to this method, it will return the item-level attributes. If a developer only wants the read-only versions of these item-level attributes, then they should set the readOnly flag of the method to true.

The following table lists the combinations of the default parameter values (listed in the first column) that can be used with the strongly typed attribute accessing method. The second column of the table lists the corresponding weakly typed method of each parameter combination.

Default Optional Parameter Values

Corresponding PXCache Method

data = null, readOnly = false

List GetAttributes(string name)

data = null, readOnly = false

List GetAttributes()

data = set, readOnly = false

IEnumerable GetAttributes(object data, string name)

data = set, readOnly = false

IEnumerable GetAttributes(object data)

data = null, readOnly = true

List GetAttributesReadonly(string name)

data = null, readOnly = true

List GetAttributesReadonly()

data = set, readOnly = true

IEnumerable GetAttributesReadonly(object data, string name)

data = set, readOnly = true

IEnumerable GetAttributesReadonly(object data)