Platform API: Support for Targeted Overrides of the PXGraph.Persist Method

In previous versions of MYOB Acumatica, a developer would generally override the PXGraph.Persist method when they needed to implement one of the following scenarios:

  • Verifying the changes made to one or more records of an entity before persisting them to the database, and preventing the changes from being persisted if the verification fails.
  • Verifying the changes made to one or more records of an entity that are to be persisted to the database, while adding some changes that should also be persisted in the same transaction in which the persist operation is executed.
  • Performing a validation or other operation right before a transaction is closed.
  • Performing an operation right after a change is successfully persisted to the database. These operations usually involve tasks that are not part of the transaction in which the persist operation is executed—such as resetting a custom cache, querying the database, or displaying a message to the user.

MYOB Acumatica2024.1 introduces the following targeted overrides of the PXGraph.Persist method, which offer more granular control to developers when they are dealing with the scenarios discussed above:

  • bool PXGraph.PrePersist(): Makes it possible for developers to define the logic that should be executed before the definition of the persisting logic. This method is triggered before a transaction is opened. The base implementation of this method raises the PXGraph.BeforePersist event.
  • void PXGraph.PerformPersist(IPersistPerformer persister): Makes it possible for developers to change the order and composition of the caches that should be persisted within the definition of the persisting logic. This method is triggered within an open transaction.
  • void PXGraph.PreCommit(): Makes it possible for developers to inject some logic just before a transaction is closed. This method is triggered within an open transaction. The base implementation of this method raises the PXGraph.OnBeforeCommit event.
  • void PXGraph.PostPersist(): Makes it possible for developers to define the logic that should be executed after the definition of the persisting logic. This method is triggered after a transaction has been closed. The base implementation of this method raises the PXGraph.AfterPersist event.
Note: When a developer overrides any of the above methods, they must call the base version of the method. The only exception to this rule is when they need to suppress the base logic for some reason.

Although developers can still override the PXGraph.Persist method, we recommend that they use the methods described in the preceding list instead. By using these methods, developers can inject their logic into the existing logic of the persist process without affecting the existing logic. This approach makes their code less error prone.

The following code shows an example of a case where a developer is using the old approach of overriding the PXGraph.Persist method. The developer needs to inject some custom logic via a graph extension in the persist process, but is unable to do so due to the current limitations of this approach.

public override void Persist()
{
 if (Document.Current != null && Document.Current.Hold == false)
 {
	foreach (POReceiptLine poReceiptLine in transactions.Select())
	 if (poReceiptLine.ReceiptQty == 0m && Document.Current.ReceiptType 
              == POReceiptType.TransferReceipt)
		transactions.Delete(poReceiptLine);
 
	ValidateDuplicateSerialsOnDropship();
 }
 
 /* A developer wants to use a graph extension to insert some custom logic here
  but there is no way to do this when overriding the PXGraph.Persist method */
 
 base.Persist();

 this.poLinesSelection.Cache.Clear();
 this.openOrders.Cache.Clear();
}

The following code shows an example of the developer being able to inject their custom logic via a graph extension, as described in the preceding scenario, by using the new targeted overrides of the PXGraph.Persist method. The logic is the same as in the previous code example, but the new approach is shown.

protected override bool PrePersist()
{
 if (Document.Current != null && Document.Current.Hold == false)
 {
	foreach (POReceiptLine poReceiptLine in transactions.Select())
	 if (poReceiptLine.ReceiptQty == 0m && Document.Current.ReceiptType 
              == POReceiptType.TransferReceipt)
		transactions.Delete(poReceiptLine);
 
	ValidateDuplicateSerialsOnDropship();
 }
 
 return base.PrePersist();
}

protected override void PostPersist()
{
 base.PostPersist();
 this.poLinesSelection.Cache.Clear();
 this.openOrders.Cache.Clear();
}

The developer can easily inject their custom logic via a graph extension by overriding the bool PXGraph.PrePersist() method, as shown in the following code example.

[PXOverride]
public bool PrePersist(Func<bool> base_PrePersist)
{
 if (!base_PrePersist())
	return false;
 
 var linesCache = Base.transactions.Cache;
 var modifiedLines = linesCache.Updated.Concat_(linesCache.Inserted);
 
 foreach (POReceiptLine line in modifiedLines)
	SyncUnassigned(line);

 
 return true;
}

The following sections provide some usage examples for the methods described in the preceding list.

Usage of the bool PXGraph.PrePersist() Method

The following code example shows how a developer may use the bool PXGraph.PrePersist() method when they need to perform a check and an assignment operation before the persist operation.

protected override bool PrePersist()
{
 if (setup.Current != null && string.IsNullOrEmpty(setup.Current.DfltLotSerClassID) 
  && !IsFeatureInstalled<FeaturesSet.lotSerialTracking>())
   {
	setup.Current.DfltLotSerClassID = INLotSerClass.GetDefaultLotSerClass(this);
   }
 return base.PrePersist();
}

Previously, a developer would implement the preceding scenario by overriding the PXGraph.Persist method in the following manner.

public override void Persist()
{
 if (setup.Current != null && string.IsNullOrEmpty(setup.Current.DfltLotSerClassID) 
  && !IsFeatureInstalled<FeaturesSet.lotSerialTracking>())
  {
	setup.Current.DfltLotSerClassID = INLotSerClass.GetDefaultLotSerClass(this);
  }
 base.Persist();
}

The following code example shows how a developer may use the bool PXGraph.PrePersist() method when they need to perform a validation before the persist operation.

protected override bool PrePersist()
{
 if (CMSetup.Select().Count == 0)
	throw new PXException(CS.Messages
        .RequiredConfigurationDataIsNotEnteredOnCurrencyManagementPreferencesForm);

 return base.PrePersist();
}

Previously, a developer would implement the preceding scenario by overriding the PXGraph.Persist method in the following manner.

public override void Persist()
{
 if (CMSetup.Select().Count == 0)
	throw new PXException(CS.Messages
        .RequiredConfigurationDataIsNotEnteredOnCurrencyManagementPreferencesForm);

 base.Persist();
}

Usage of the void PXGraph.PostPersist() Method

The following code example shows how a developer may use the void PXGraph.PostPersist() method when they need to perform some operations after the persist operation.

protected override void PostPersist()
{
 base.PostPersist();
 this.Quotes.Cache.Clear();
 this.Quotes.View.Clear();
 this.Quotes.Cache.ClearQueryCache();
 this.Quotes.View.RequestRefresh();
}

Previously, a developer would implement the preceding scenario by overriding the PXGraph.Persist method in the following manner.

public override void Persist()
{
 base.Persist();
 this.Quotes.Cache.Clear();
 this.Quotes.View.Clear();
 this.Quotes.Cache.ClearQueryCache();
 this.Quotes.View.RequestRefresh();
}

Usage of the void PXGraph.PerformPersist(IPersistPerformer persister) Method

Suppose that a developer needs to use a graph extension to find some records of some specific entities and persist them to the database in the scope of another graph. This graph extension should first search for those records and store them into a field by overriding the bool PXGraph.PrePersist() method. Then at the end of the void PXGraph.PerformPersist(IPersistPerformer persister) method, the graph extension should pass those stored records to a special method that creates a new graph in which those records should be saved, and saves them. Since the developer is calling this special method in the scope of the void PXGraph.PerformPersist(IPersistPerformer persister) method, it is automatically available in the scope of the same transaction that is used in the initial graph (the one that overrides the persist methods). Hence, the developer does not need to open another transaction.

The following code example shows how a developer may use the void PXGraph.PerformPersist(IPersistPerformer persister) method to implement the scenario described above.

[PXOverride]
public bool PrePersist(Func<bool> base_PrePersist)
{
 _affectedOrders = GetAffectedEntities().ToArray();
 
 return base_PrePersist();
}
 
[PXOverride]
public void PerformPersist(PXGraph.IPersistPerformer persister,
 Action<PXGraph.IPersistPerformer> base_PerformPersist)
{
 base_PerformPersist(persister);
 
 if (_affectedOrders != null)
 {
	ProcessAffectedEntities(_affectedOrders);
	_affectedOrders = null;
 }
}
 
[PXOverride]
public void PostPersist(Action base_PostPersist)
{
 _affectedOrders = null;
}

The following example shows how the logic of the void PXGraph.PerformPersist(IPersistPerformer persister) method is overridden. Note that the void PXGraph.PerformPersist(IPersistPerformer persister) base method is not called anywhere in this code because the developer's goal is to take full control of the persisting process.

protected override void PerformPersist(IPersistPerformer persister)

{

    persister.Insert(APPayment_DocType_RefNbr.Cache);

    persister.Update(APPayment_DocType_RefNbr.Cache);


    persister.Update(APDocument.Cache);


    persister.Update(APTran_TranType_RefNbr.Cache);


  persister.Update(APPaymentChargeTran_DocType_RefNbr.Cache);


  persister.Insert(APTaxTran_TranType_RefNbr.Cache);

    persister.Update(APTaxTran_TranType_RefNbr.Cache);

  persister.Insert(SVATConversionHistory.Cache);

  persister.Update(SVATConversionHistory.Cache);


    persister.Insert(APAdjust_AdjgDocType_RefNbr_VendorID.Cache);

    persister.Update(APAdjust_AdjgDocType_RefNbr_VendorID.Cache);

    persister.Delete(APAdjust_AdjgDocType_RefNbr_VendorID.Cache);


    persister.Insert<APHist>();

    persister.Insert<CuryAPHist>();

    persister.Insert<APTranPost>();


    persister.Insert(AP1099Year_Select.Cache);

    persister.Insert(AP1099History_Select.Cache);


    persister.Update(CurrencyInfo_CuryInfoID.Cache);


  persister.Insert<CADailySummary>();

  persister.Insert<PMCommitment>();

  persister.Update<PMCommitment>();

  persister.Delete<PMCommitment>();

  persister.Insert<PMHistoryAccum>();

  persister.Insert<PMBudgetAccum>();

  persister.Insert<PMForecastHistoryAccum>();


  persister.Update<APTax>();

}

Methods of the IPersistPerformer Interface

An IPersistPerformer interface is supplied as the parameter for the void PXGraph.PerformPersist(IPersistPerformer persister) method, as described in the preceding section. The following table lists the methods that this interface implements. Note that a developer does not need to implement this interface at all.

Method Description
ISet<Type> PersistedTypes { get; } Represents a set of caches whose data rows have already been persisted.
void Insert(PXCache cache); Inserts new data rows in the database.
void Insert(Type cacheType); Inserts new data rows in the database.
void Insert<TTable>() where TTable : class, IBqlTable, new(); Inserts new data rows in the database.
void Update(PXCache cache); Saves changed data rows in the database.
void Update(Type cacheType); Saves changed data rows in the database.
void Update<TTable>() where TTable : class, IBqlTable, new(); Saves changed data rows in the database.
void Delete(PXCache cache); Removes deleted data rows from the database.
void Delete(Type cacheType); Removes deleted data rows from the database.
void Delete<TTable>() where TTable : class, IBqlTable, new(); Removes deleted data rows from the database.