PXRestrictor 属性

介绍

PXSelectorAttribute 属性(也称为选择器)虽然重要且经常使用,但有两个主要缺点:

  • 如果没有找到满足选择条件的项目,它会给出一个无信息的消息 <object_name> cannot be found in the system
  • 如果更新记录的其他字段但选择器引用的对象已更改且不再满足其条件,则会生成相同的错误消息。这种行为显然是错误的,因为准则不得追溯。

PXRestrictorAttribute(也称为限流器)可用于解决这些问题。

细节

PXRestrictorAttribute 不能单独工作; 它应该始终与 PXSelectorAttribute 配对。使用不带选择器的限制器将不起作用。

限制器在同一个字段上找到选择器,向其中注入一个附加条件和相应的错误消息。限制器条件通过布尔 AND 附加到选择器条件,如果引用的对象违反限制器约束,则会生成相应的错误消息。此外,如果引用的对象已更改且不再符合限制器条件,则更改引用对象的任何其他字段时不会生成任何错误消息。

一般用法:

[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; }

多个限制器可以与一个选择器属性一起使用。在这种情况下,所有其他限制器条件以非确定的顺序应用。一旦违反任何条件,就会生成相应的错误消息。

选择器本身的 Where<> 条件所有限制器条件之后应用。

[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; }

选项

PXRestrictorAttribute 的构造函数有三个参数:

  1. 限制器的附加条件。此 BQL 类型必须实现 IBqlWhere 接口。
  2. 相应的错误消息。消息可以包含格式元素(花括号)以显示上下文。消息必须是在可本地化的静态类(例如 PX.Objects.GL.Messages)中定义的字符串常量。
  3. 一系列字段类型。这些字段必须属于当前对象,并且必须实现 IBqlField 接口。字段的值将用于错误消息格式化。

此外,还有几个选项可指定限制器行为。

重写继承的限制器

ReplaceInherited 属性指示当前限制器是否应覆盖继承的限制器。如果此属性设置为 true,则将替换所有继承的限制器(放置在任何聚合属性或基本属性上)。

替换继承的限制器:

 [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; }

请注意,如果存在合理的替代方案,我们不建议你在应用程序代码中使用 ReplaceInherited 属性。此属性主要用于自定义。

全局缓存

CacheGlobal 支持全局字典功能,与 PXSelectorAttribute 相同。

使用建议

仅使用限制器条件

当限制器和选择器一起使用时,后者不应包含 IBqlWhere 子句。理想情况下,所有条件都应该转移到限制器中。此方法提供了更加用户友好的错误消息,并消除了不必要的追溯错误。

一个理想的例子:

[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; }

可能的追溯错误:

[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; }