Aggregation of Attributes
You can aggregate functionality from any number of attributes into a single attribute by using an aggregator attribute. An aggregator attribute is derived from the base PX.Data.PXAggregateAttribute attribute, which contains the logic that handles the aggregation process.
Suppose that you have the following definitions for the CustomerID
field
in the ARInvoice
and SOOrder
data access classes.
public class ARInvoice : IBqlTable
{
[PXDBInt()]
[PXUIField(DisplayName = "Customer")]
[PXDefault()]
[PXSelector(typeof(Search<BAccountR.bAccountID,
Where<BAccountR.status, Equal<BAccount.status.active>>>))]
public override Int32? CustomerID
}
public class SOOrder : IBqlTable
{
[PXDBInt()]
[PXUIField(DisplayName = "Customer")]
[PXDefault()]
[PXSelector(typeof(Search<BAccountR.bAccountID,
Where<BAccountR.status, Equal<BAccount.status.active>>>))]
public override Int32? CustomerID
}
For the CustomerID
field specified in the classes in the code example
above, you can aggregate the set of attributes by combining these attributes into a single
class, as shown in the following code example.
[PXDBInt()]
[PXUIField(DisplayName = "Customer")]
[PXDefault()]
[PXSelector(typeof(Search<BAccountR.bAccountID,
Where<BAccountR.status, Equal<BAccount.status.active>>>))]
public class CustomerActiveAttribute : PXAggregateAttribute { }
public class ARInvoice : IBqlTable
{
[CustomerActive]
public override Int32? CustomerID { get; set; }
}
public class SOOrder : IBqlTable
{
[CustomerActive]
public override Int32? CustomerID { get; set; }
}
In the code example above, you have aggregated all the attributes that are declared for the
CustomerID
field in the ARInvoice
and
SOOrder
DACs by combining and declaring them in the
CustomerActiveAttribute
class. This class is extending the
PXAggregateAttribute class, which serves as the base class from which
an aggregator attribute is derived. You then simply replace all the originally defined
attributes with the CustomerActive
aggregator attribute for the
CustomerID
field in the ARInvoice
and
SOOrder
DACs.
The PXAggregateAttribute attribute also contains the GetAttributes method, which returns the collection of all the combined attributes. The signature of this method is shown in the following code example.
public PXEventSubscriberAttribute[] GetAttributes()
Approaches to Adding Attributes to an Aggregator Attribute
- Add the attributes in code during runtime
- Declare the attributes in the aggregator attribute's class definition
public VendorAttribute(Type search, params Type[] fields)
{
...
var attr = new PXDimensionSelectorAttribute(DimensionName, cmd,
typeof(BAccountR.acctCD), fields));
_Attributes.Add(attr);
...
}
In the code example above, the PXDimensionSelectorAttribute attribute has been added to the constructor of the PX.Objects.AP.VendorAttribute aggregator attribute. Note that in this case, the PXDimensionSelectorAttribute attribute itself is an aggregator attribute, which means that you can recursively declare aggregator attributes on other aggregator attributes (though this approach is not recommended).
Best Practices for Working with an Aggregator Attribute
The system determines a flattened (non-hierarchical) set of attributes added to a DAC field by an aggregator attribute by performing the following general steps (in no particular order):
- The system adds the aggregator attribute itself and all of its base types.
- The system adds the attributes aggregated by the aggregator attribute and all of their base types.
- For all the aggregator attributes in the attributes acquired in the preceding step, the system recursively performs the preceding two steps until there are no more new attributes left to be added to the final set of attributes.
Note that PXCache automatically handles aggregator attributes. This means that calls to the PXCache.GetAttributes and PXCache.GetAttributesReadonly methods will return a list of flattened set of attributes.
You should try to avoid doing the following when you are working with an aggregator attribute:
- Using complex combinations of multiple aggregator attributes on a DAC field. Although it is technically possible to correctly create these combinations, a change in the aggregated attributes of one of the aggregator attributes may easily break such a combination.
- Declaring an aggregator attribute on another aggregator attribute.
Common Use Cases of an Aggregator Attribute
You commonly use aggregator attributes to encode a fixed set of related attributes for some functionality in a single attribute. Additionally, you may use an aggregator attribute to define some business logic, as shown in the following code example.
[PXDBInt]
[PXUIField(DisplayName = "Project", Visibility = PXUIVisibility.Visible)]
[PXRestrictor(typeof(Where<PMProject.isActive, Equal<True>,
Or<PMProject.nonProject, Equal<True>>>),
Messages.InactiveContract, typeof(PMProject.contractCD))]
[PXRestrictor(typeof(Where<PMProject.isCompleted, Equal<False>>),
Messages.CompleteContract, typeof(PMProject.contractCD))]
[PXRestrictor(typeof(Where<PMProject.isCancelled, Equal<False>>),
Messages.CancelledContract, typeof(PMProject.contractCD))]
[PXRestrictor(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
{...}
One of the most frequently used aggregator attributes is PX.Data.PXEntityAttribute, which serves as a base class for many other aggregator attributes, such as the one shown in the code example above. More than 200 aggregator attributes are derived from the PXEntityAttribute aggregator attribute. Other commonly used aggregator attributes include PX.Objects.GL.PeriodIDAttribute and PX.Objects.CM.CurrencyInfoAttribute.