Hi Community,
In this post I want to share with you an article on the attributes aggregation in Acumatica.
Attributes on DAC Field Properties
In Acumatica, we use attributes to store common business and platform logic that does not depend on any particular screen or DAC. This post describes only Acumatica attributes derived from the PXEventSubscriberAttribute
attribute. It is based on Acumatica ERP 2023 R1 although most approaches described in it could be applied to other versions of Acumatica ERP.
You can view Acumatica attributes as features which you can add to DAC field properties. For example, attributes can add one of the following features:
- Specify data type for the field's interaction with the database and runtime using data type attributes like
PXStringAttribute
orPXDBStringAttribute
- Configure UI representation of the field with
PXUIFieldAttribute
- Provide default values with
PXDefaultAttribute
- Configure master-detail relationship or referential integrity
- Add components with common business logic like contact info, multi-currency, or tax calculation
- etc
You can view attributes on declaration of a DAC field property as a list of features applied to the DAC field as shown in the following example:
pPXDBCalced(typeof(APRegister.curyInitDocBal), typeof(decimal))]
PXCurrency(typeof(APRegister.curyInfoID), typeof(APRegister.initDocBal), BaseCalc = false)]
PXUIField(DisplayName = "Migrated Balance", Visibility = PXUIVisibility.SelectorVisible)]
PXDefault(TypeCode.Decimal, "0.0", PersistingCheck = PXPersistingCheck.Nothing)]
public virtual decimal? DisplayCuryInitDocBal
{
get;
set;
}
Aggregator Attributes
An aggregator attribute is a special type of Acumatica attributes that allows you to aggregate functionality from several attributes in one attribute. Hence its name - aggregator attribute. Aggregator attributes are derived from the base PX.Data.PXAggregateAttribute
attribute which contains logic to handle the aggregation process. It also provides the GetAttributes
method to access aggregated attributes: to get the collection of all attributes combined in the current aggregator attribute. The signature of the GetAttributes
method is shown in the following code:
public PXEventSubscriberAttributeb] GetAttributes()
The PXAggregateAttribute
attribute itself does not aggregate any attributes. Aggregating is performed by derived aggregator attributes.
Aggregated Attributes
There are two approaches to add aggregated attributes to the aggregator attribute:
- Add aggregated attributes in code during runtime. This is usually done in the attribute's constructor. For example, the following code shows the addition of the
PXDimensionSelectorAttribute
attribute in thePX.Objects.AP.VendorAttribute
constructor:Aggregated dimension selector attribute addition in VendorAttribute - Declare the aggregated attributes on the aggregator attribute. The following code shows attributes declared on
VendorAttribute
:Aggregated attributes declared on VendorAttribute The aggregated attributes are collected from the entire attribute's class hierarchy.
As you can see, both approaches can be combined. Moreover, you can recursively declare aggregator attributes on aggregator attributes. In the example above, VendorAttribute
adds PXDimensionSelectorAttribute
in its constructor. The PXDimensionSelectorAttribute
attribute is an aggregator attribute itself, it aggregates PXDimensionAttribute
and PXSelectorAttribute
attributes as shown in the following code:
![](https://uploads-us-west-2.insided.com/acumatica-en/attachment/bd902a75-8238-4e36-beef-d40470d924e1.png)
Best Practices
Complex aggregator attributes can be hard to reason about. It is easy to create aggregator with inconsistent aggregated attributes since the attribute declarations could be non-transparent. For example, you can declare PXString
on the attribute itself and PXInt
could be declared on one of the base attributes. Some of such inconsistencies are reported by Acuminator, others will fail at runtime. Some inconsistent attribute combinations may still remain as subtle bugs.
Remember, that explicit declaration is usually better then an implicit one!
Try to avoid the following techniques while working with aggregator attribute:
- Avoid complex combinations of several aggregators on a DAC field property. While it is possible to correctly make such combinations, they are brittle and changes in aggregated attributes in one of the aggregators may break the combination.
- Avoid aggregators on aggregators
The system determines a flattened set of all attributes added to a DAC field by an aggregator attribute in the following order:
- Aggregator attribute itself and all its base types.
- Attributes aggregated by the aggregator attribute and all its base types.
- For all aggregator attributes in the attributes acquired in step 2, you need to recursively perform steps 1 and 2 until there is no more new attributes to add.
As you can see, it can be difficult to reason about the aggregated attributes.
Use Cases and Examples
The most common use case for aggregator attributes is to encode a fixed set of related attributes for some feature in one attribute. Additionally, the aggregator attribute may contain its own business logic as shown in the following RetainageAmountAttribute
:
ePXDBInt]
IPXUIField(DisplayName = "Project", Visibility = PXUIVisibility.Visible)]
lPXRestrictor(typeof(Where<PMProject.isActive, Equal<True>, Or<PMProject.nonProject, Equal<True>>>), Messages.InactiveContract, typeof(PMProject.contractCD))]
DPXRestrictor(typeof(Where<PMProject.isCompleted, Equal<False>>), Messages.CompleteContract, typeof(PMProject.contractCD))]
DPXRestrictor(typeof(Where<PMProject.isCancelled, Equal<False>>), Messages.CancelledContract, typeof(PMProject.contractCD))]
DPXRestrictor(typeof(Where<PMProject.baseType, NotEqual<CT.CTPRType.projectTemplate>,
And<PMProject.baseType, NotEqual<CT.CTPRType.contractTemplate>>>), Messages.TemplateContract, typeof(PMProject.contractCD))]
public class ActiveProjectOrContractBaseAttribute : PXEntityAttribute, IPXFieldVerifyingSubscriber
{...}
In some cases, an aggregator has only the business logic and an aggregated PXUIFieldAttribute
with predefined DisplayName
and other properties as shown in the following RetainageAmountAttribute
:
ePXUIField(DisplayName = "Retainage Amount",
Visibility = PXUIVisibility.Visible,
FieldClass = nameof(FeaturesSet.Retainage))]
public class RetainageAmountAttribute : PXAggregateAttribute {...}
One of the most frequently used aggregator attributes is PXEntityAttribute
which serves as a base class for many application attributes (for example, PX.Objects.AP.VendorAttribute
). Almost 200 attributes are derived from it.
Another frequently used aggregator is PX.Objects.GL.PeriodIDAttribute
attribute with 31 inheritors. Application code also contains other frequently used aggregator attributes without many inheritors such as PX.Objects.CM.CurrencyInfoAttribute
.
That’s all about attributes aggregation! Thank you for your attention!