Platform API: Support for a Strongly Typed Facade for the PXCache Object
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
Use of the PXCache Object
The PXCache
PXCache<SOOrder> orderCache = graph.Caches<SOOrder>();
A developer can also directly cast a PXCache object to PXCache
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
- 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 InvalidCastExceptionexception. 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)]; } }
ThePXCache<ARInvoice> cache = this.Caches<ARInvoice>();line in the code above results in anInvalidCastExceptionexception 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 theARDocumentsview in the code above, thegraph.Caches[typeof(ARInvoice)]indexer will return an instance of thePXCache<ARRegister>type. Then the system will try to downcast it to thePXCache<ARInvoice>type to match the return type of thegraph.Caches<ARInvoice>()method. The PXCacheobject 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 PXCacheobject 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
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 PXCachePXCache<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
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
IEnumerable Inserted { get; }
IEnumerable
IEnumerable Updated { get; }
IEnumerable
IEnumerable Deleted { get; }
IEnumerable
IEnumerable Cached { get; }
IEnumerable
IEnumerable Dirty { get; }
IEnumerable
Suppose that a developer declares and initializes a PXCachePXCache<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
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
PXCache
PXCache Instance Methods
PXCache
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
TExtension GetExtension
TExtension GetExtension
TNode GetMain
object GetMain
TNode GetMain
TNode Extend
object Extend
TNode Extend
Strongly Typed Overloads for the Data Manipulation Methods
For the data manipulation methods of the strongly typed PXCacherow => 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
void SetValueExt(object data, string fieldName, object value)
void SetValueExt
void SetDefaultExt
void SetDefaultExt(object data, string fieldName, object value = null)
void SetDefaultExt
void SetValuePending
void SetValuePending(object data, string fieldName, object value)
void SetValuePending
TValue GetValuePending
object GetValuePending(object data, string fieldName)
object GetValuePending
TValue GetValueOriginal
object GetValueOriginal(object data, string fieldName)
object GetValueOriginal
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
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
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
bool RaiseCommandPreparing(string name, object row, object value, ...)
bool RaiseCommandPreparing
bool RaiseFieldDefaulting
bool RaiseFieldDefaulting(string name, object row, out object newValue)
bool RaiseFieldDefaulting
bool RaiseFieldUpdating
bool RaiseFieldUpdating(string name, object row, ref object newValue)
bool RaiseFieldUpdating
bool RaiseFieldVerifying
bool RaiseFieldVerifying(string name, object row, ref object newValue)
bool RaiseFieldVerifying
void RaiseFieldUpdated
void RaiseFieldUpdated(string name, object row, object oldValue)
void RaiseFieldUpdated
bool RaiseExceptionHandling
bool RaiseExceptionHandling(string name, object row, object newValue, Exception exception)
bool RaiseExceptionHandling
Suppose that a developer declares and initializes a PXCache
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
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
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
data = null, readOnly = false
List
data = set, readOnly = false
IEnumerable
data = set, readOnly = false
IEnumerable
data = null, readOnly = true
List
data = null, readOnly = true
List
data = set, readOnly = true
IEnumerable
data = set, readOnly = true
IEnumerable