Another process has updated the {Table} record. Your changes will be lost

Hello everybody,

Today a want to share with you couple approaches that can help you to fix a famous Acumatica’s error:

“Another process has updated the {Table} record. Your changes will be lost”

You can get this error when you open two the same screens with the same record from DB and then modify data on both screens and click Save.

Or maybe you have custom logic that run some functionality in PXLongOperation (run in multi-threading mode) and one record from DB can be modified by different threads and then during persisting you will get this error.

Or maybe you need to create and to release several documents during code logic that use the same record in DB (for example Create/Release Invoice and then Create/Release Payment that updates ARRegister table).

We get this error when try persist in DB record that was modified before by another graph or process and record has another version (another value) of TimeStamp field.

So, TimeStamp control version in Acumatica and DB is done using field TimeStamp (or Tstamp) with [PXDBTimestamp] attribute in the DAC table. When record is updated, the TimeStamp field increase own value at 1. Every time when a graph persist record to DB, it verifies version of the record to be sure that the record is not changed by another process.

We have three approaches that can help us fix this error and run code logic till the end. (Two first approaches you can find in source code of Acumatica site, Acumatica uses it often):

  1. Use SelectTimeStamp() method of the graph:

When you invoke this methos, graph selects and sets TimeStamp field with new version and then you can persist data to DB.

Here one of examples from Acumatica’s VendorMaint graph, Persist method:

public override void Persist()
{
    using (PXTransactionScope ts = new PXTransactionScope())
    {
        bool persisted = false;
        try
        {
            BAccountRestrictionHelper.Persist();
            persisted = (base.Persist(typeof(Vendor), PXDBOperation.Update) > 0);
        }
        catch
        {
            Caches[typeof(Vendor)].Persisted(true);
            throw;
        }
        base.Persist();
        if (persisted)
        {
            base.SelectTimeStamp();
        }
        ts.Complete();
    }
}

 

Another example:

private void PrepareForPOCreate(List<SOLine> listSO)
{
    foreach (SOLine item in listSO)
    {
        item.Qty = item.GetExtension<APSOLineExt>().UsrMasterSOQty;
 
        var inventoryItem = UpdatePOOrderExtension.CheckDefVendor(this.Base, item.InventoryID);
 
        if (inventoryItem == null)
        {
            Base.Caches<SOLine>().SetValueExt<SOLine.pOCreate>(item, false);
            Base.Transactions.Cache.Update(item);
        }
    }
 
    Base.SelectTimeStamp();
    Base.Save.Press();
}

 2. Use PXTimeStampScope.SetRecordComesFirst() method:

Sometimes first approach doesn’t help and error still is raised.

 By default, Acumatica checks TimeStamp version of graph only when key fields are modified. But SetRecordComesFirst() method activates RecordComesFirst flag and Acumatica check TimeStamp version for all changes in cache.

Here is example how to create and release Payment:

private static ARPayment CreateAndReleasePayment(ARInvoice arInvoice)
 {
     ARPaymentEntry paymentEntry = PXGraph.CreateInstance<ARPaymentEntry>();
 
     var arAdjust = SelectFrom<ARAdjust>.Where<ARAdjust.adjdRefNbr.IsEqual<@P.AsString>>.View.Select(paymentEntry, arInvoice.RefNbr)?.TopFirst;
 
     if (arAdjust?.AdjgRefNbr == null)
     {
         paymentEntry.CreatePayment(arInvoice, null, arInvoice.DocDate, arInvoice.FinPeriodID, false);
 
         paymentEntry.Document.Current.ExtRefNbr = arInvoice.DocDesc;
         paymentEntry.Document.Current.BranchID = arInvoice.BranchID;
         paymentEntry.Document.UpdateCurrent();
         paymentEntry.Save.Press();
     }
     else
     {
         paymentEntry.Document.Current = SelectFrom<ARPayment>.Where<ARPayment.refNbr.IsEqual<@P.AsString>>.View.Select(paymentEntry, arAdjust.AdjgRefNbr)?.TopFirst;
     }
 
     ARPayment arPayment = paymentEntry.Document.Current;
 
     paymentEntry.Clear();
 
     ARRegister doc = arPayment;
     List<ARRegisterlist = new List<ARRegister>() { doc };
 
     using (new PXTimeStampScope(null))
     {
         PXTimeStampScope.SetRecordComesFirst(typeof(ARRegister), true);
 
         try
         {
             ARDocumentRelease.ReleaseDoc(list, false);
         }
         catch (PXException e)
         {
             PXTrace.WriteError(e.Message);
         }
     }
 
     return arPayment;
 }

Acumatica’s usage of this method in ARInovoiceEntry:

public void ReleaseProcess(List<ARRegister> list)
        {
            PXTimeStampScope.SetRecordComesFirst(typeof(ARInvoice), true);
 
            ARDocumentRelease.ReleaseDoc(list, falsenull, (ab) => { });
        }

 3. Use PXTimeStampScope.DuplicatePersisted() method:

 

The most difficult approach, but helps and fixes the error, if first and second approaches don’t work. There are no examples in Acumatica’s site source code, but below I use it two times (override persist methods):

Lazy is not necessary to use, it is just for performance, because I had long and heavy logic of process on my custom processing screen.

public class ARPaymentEntryExt : PXGraphExtension<ARPaymentEntry>
{
    public static bool IsActive() => true;
 
    [PXOverride]
    public virtual void Persist(Action baseMethod)
    {
        var updInvoices = Base.ARInvoice_DocType_RefNbr.Cache.Updated.Cast<ARInvoice>().ToList();
        var updPayments = Base.Document.Cache.Updated.Cast<ARPayment>().ToList();
 
        baseMethod?.Invoke();
 
        var lazyTempGraph = new Lazy<PXGraph>(() => PXGraph.CreateInstance<PXGraph>());
        foreach (ARInvoice updInvoice in updInvoices)
        {
            PXTimeStampScope.DuplicatePersisted(lazyTempGraph.Value.Caches[typeof(ARRegister)], updInvoice, typeof(ARInvoice));
            PXTimeStampScope.DuplicatePersisted(lazyTempGraph.Value.Caches[typeof(ARPayment)], updInvoice, typeof(ARInvoice));
        }
        foreach (ARPayment updPayment in updPayments)
        {
            PXTimeStampScope.DuplicatePersisted(lazyTempGraph.Value.Caches[typeof(ARRegister)], updPayment, typeof(ARPayment));
        }
    }
}
public class ARDocumentReleaseExt : PXGraphExtension<ARReleaseProcess>
{
    public static bool IsActive() => true;
 
    [PXOverride]
    public virtual void Persist(Action baseMethod)
    {
        var updRegisters = Base.ARDocument.Cache.Updated.Cast<ARRegister>().ToList();
        baseMethod?.Invoke();
 
        var lazyTempGraph = new Lazy<PXGraph>(() => PXGraph.CreateInstance<PXGraph>());
 
        foreach (ARRegister updRegister in updRegisters)
        {
            PXTimeStampScope.DuplicatePersisted(lazyTempGraph.Value.Caches[typeof(ARPayment)], updRegister, typeof(ARRegister));
            PXTimeStampScope.DuplicatePersisted(lazyTempGraph.Value.Caches[typeof(ARInvoice)], updRegister, typeof(ARRegister));
        }
    }
}

 Summary

As you can see, other process has updated is kind of commonly seen error in Acumatica, and you now seen at least three ways of how to handle that

How To Pass Data Into Processing Method With Help Of Lambda Expression

How to pass data into processing method with help of lambda expression

Hello everybody,

today I want to leave a short note on how to pass some additional parameters into Processing method of processing page with help of lambda expression. Syntax is pretty simple:

public class SSShipmentDateResetter : PXGraph<SSShipmentDateResetter>
{
	public PXFilter<ShipmentFilter> ShipmentFilter;
 
	public PXFilteredProcessing<SOShipment, ShipmentFilter> ShipmentsForProcessing;
 
	public SSShipmentDateResetter()
	{
		var shipmentFilter = ShipmentFilter.Current;
		ShipmentsForProcessing.SetProcessDelegate(shipments =>
		{
			ResetDate(shipmentFilter, shipments);
		});
	}
 
	public static void ResetDate(ShipmentFilter filter,  List<SOShipment> shipments)
	{
		
	}
}

for me such approach is a bit easier to use as it involes a bit less amout of typing.

If you want to have even simpler syntax, then following sample also may work for you:

public SSShipmentDateResetter()
{
	var filter = ShipmentFilter.Current;
	ShipmentsForProcessing.SetProcessDelegate(shipments => ResetDate(filter, shipments));
}

For simplicity sake, I've showed only constructor.

No Comments

 

Add a Comment