Skip to main content

Attributes Aggregation

  • 14 June 2024
  • 1 reply
  • 60 views

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 or PXDBStringAttribute
  • 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:

  1. 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 the PX.Objects.AP.VendorAttribute constructor:
    Aggregated dimension selector attribute addition in VendorAttribute
  2. 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:

Aggregated attributes addition in PXDimensionAttribute

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:

  1. Aggregator attribute itself and all its base types.
  2. Attributes aggregated by the aggregator attribute and all its base types.
  3. 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!

1 reply

Userlevel 7
Badge

Thank you for sharing this tip with the community @snikomarov36!

Reply