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