The PXSelectorAttribute attribute (also referred to as the selector), while vital and frequently used, has however two major drawbacks:
"<object_name> cannot be found in the system"
if no items are found to satisfy the selector condition.The PXRestrictorAttribute
(also referred to as the restrictor) can be used to solve these problems.
PXRestrictorAttribute
does not work alone; it should always be paired with a PXSelectorAttribute
. Using the restrictor without the selector will have no effect.
The restrictor finds the selector on the same field, injecting into it an additional condition and the corresponding error message. The restrictor condition is appended to the selector condition via a boolean AND, and an appropriate error message is generated if the referenced object violates the restrictor constraint. Also, if the referenced object has changed and no longer meets the restrictor condition, no error messages are produced when you change any other fields of the referencing object.
General usage:
[PXDBInt] [PXSelector(typeof(Search<FAClass.assetID, Where<FAClass.recordType, Equal<FARecordType.classType>>>), typeof(FAClass.assetCD), typeof(FAClass.assetTypeID), typeof(FAClass.description), typeof(FAClass.usefulLife), SubstituteKey = typeof(FAClass.assetCD), DescriptionField = typeof(FAClass.description), CacheGlobal = true)] [PXRestrictor(typeof(Where<FAClass.active, Equal<True>>), Messages.InactiveFAClass, typeof(FAClass.assetCD))] [PXUIField(DisplayName = "Asset Class", Visibility = PXUIVisibility.Visible)] public virtual int? ClassID { get; set; }
Multiple restrictors can be used with one selector attribute. In this case, all additional restrictor conditions are applied in a non-determined order. Once any condition is violated, the appropriate error message is generated.
The Where<>
condition of the selector itself is applied after all restrictor conditions.
[PXDefault] // An aggregate attribute containing the selector inside. // - [ContractTemplate(Required = true)] [PXRestrictor(typeof(Where<ContractTemplate.status, Equal<Contract.status.active>>), Messages.TemplateIsNotActivated, typeof(ContractTemplate.contractCD))] [PXRestrictor(typeof(Where<ContractTemplate.effectiveFrom, LessEqual<Current<AccessInfo.businessDate>>, Or<ContractTemplate.effectiveFrom, IsNull>>), Messages.TemplateIsNotStarted)] [PXRestrictor(typeof(Where<ContractTemplate.discontinueAfter, GreaterEqual<Current<AccessInfo.businessDate>>, Or<ContractTemplate.discontinueAfter, IsNull>>), Messages.TemplateIsExpired)] public virtual int? TemplateID { get; set; }
The constructor of PXRestrictorAttribute takes three parameters:
IBqlWhere
interface.PX.Objects.GL.Messages
).IBqlField
interface. The values of the fields will be used for error message formatting.Also, there are several options that specify the restrictor behavior.
The ReplaceInherited
property indicates whether the current restrictor should override the inherited restrictors. If this property is set to true, then all inherited restrictors (placed on any aggregate attributes or base attribute) will be replaced.
Replacing inherited restrictors:
[CustomerActive(Visibility = PXUIVisibility.SelectorVisible, Filterable = true, TabOrder = 2)] [PXRestrictor(typeof(Where<Customer.status, Equal<CR.BAccount.status.active>, Or<Customer.status, Equal<CR.BAccount.status.oneTime>, Or<Customer.status, Equal<CR.BAccount.status.hold>, Or<Customer.status, Equal<CR.BAccount.status.creditHold>>>>>), Messages.CustomerIsInStatus, typeof(Customer.status), ReplaceInherited = true)] // Replaced all restrictors from CustomerActiveAttribute [PXUIField(DisplayName = "Customer")] [PXDefault()] public override int? CustomerID { get; set; }
Please note that we do not advise that you use the ReplaceInherited
property in application code when reasonable alternatives exist. This property is primarily intended to be used in customizations.
CacheGlobal
supports global dictionary functionality in the same way as in PXSelectorAttribute
.
When restrictors and a selector are used together, the latter should not contain the IBqlWhere
clause. Ideally, all conditions should be moved into restrictors. This approach provides more user-friendly error messages and eliminates unnecessary retroactive errors.
An ideal example:
[PXDBString(5, IsFixed = true, IsUnicode = false)] [PXUIField(DisplayName = "Type", Required = true)] [PXSelector(typeof(EPActivityType.type), DescriptionField = typeof(EPActivityType.description))] [PXRestrictor(typeof(Where<EPActivityType.active, Equal<True>>), Messages.InactiveActivityType, typeof(EPActivityType.type))] [PXRestrictor(typeof(Where<EPActivityType.isInternal, Equal<True>>), Messages.ExternalActivityType, typeof(EPActivityType.type))] public virtual string Type { get; set; }
Possible retroactive errors:
[PXDBInt] [PXUIField(DisplayName = "Contract")] [PXSelector(typeof(Search2<Contract.contractID, LeftJoin<ContractBillingSchedule, On<Contract.contractID, Equal<ContractBillingSchedule.contractID>>>, Where<Contract.isTemplate, NotEqual<True>, And<Contract.baseType, Equal<Contract.ContractBaseType>, And<Where<Current<CRCase.customerID>, IsNull, Or2<Where<Contract.customerID, Equal<Current<CRCase.customerID>>, And<Current<CRCase.locationID>, IsNull>>, Or2<Where<ContractBillingSchedule.accountID, Equal<Current<CRCase.customerID>>, And<Current<CRCase.locationID>, IsNull>>, Or2<Where<Contract.customerID, Equal<Current<CRCase.customerID>>, And<Contract.locationID, Equal<Current<CRCase.locationID>>>>, Or<Where<ContractBillingSchedule.accountID, Equal<Current<CRCase.customerID>>, And<ContractBillingSchedule.locationID, Equal<Current<CRCase.locationID>>>>>>>>>>>>, OrderBy<Desc<Contract.contractCD>>>), DescriptionField = typeof(Contract.description), SubstituteKey = typeof(Contract.contractCD), Filterable = true)] [PXRestrictor(typeof(Where<Contract.status, Equal<Contract.status.active>>), Messages.ContractIsNotActive)] [PXRestrictor(typeof(Where<Current<AccessInfo.businessDate>, LessEqual<Contract.graceDate>, Or<Contract.expireDate, IsNull>>), Messages.ContractExpired)] [PXRestrictor(typeof(Where<Current<AccessInfo.businessDate>, GreaterEqual<Contract.startDate>>), Messages.ContractActivationDateInFuture, typeof(Contract.startDate))] [PXFormula(typeof(Default<CRCase.customerID>))] [PXDefault(PersistingCheck = PXPersistingCheck.Nothing)] public virtual int? ContractID { get; set; }