Another process has updated the record, your changes will be lost

Hello everybody,

Today a want to share with you couple approaches that can help you to fix the 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 try to Save.

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

Or maybe you need to create and to release several documents while code modifies the same record or uses same inherited table. For example, you create and release invoice, and then create and release payment, both of them update the ARRegister table.

 

Generally speaking, we get this error when someone (something) tries to store in the DB a record that was modified earlier and the record now has another stage (i.e., another value). Means there is a conflict between user’s copy of data and actually stored in database.

In what way may it come? As you may know Acumatica persists rows one by one (in most cases). And before persisting to DB, Acumatica keeps data and changes in a graph’s specified cache. So, when a record is opened in several screens, the record will be kept in different caches. Thus, it might be that several different screens might have different versions of same record with different changes. That may cause concurrent updates, when a database needs to understand changes’ priority.

To handle concurrent updates Acumatica uses the special Timestamp data field. In the DAC this field has PXDBTimestamp attribute. In database this column has name TStamp with timestamp data type.

Acumatica handles the Timestamp field automatically and checks the record version every time the record is modified. When a record is updated, the Timestamp field is increased, in this way every next version of a record will have bigger Timestamp value than pervious.

Then, each time a graph needs to persist a record or changes to the DB, Acumatica verifies the version of the record to ensure that the record has not been modified by another process. And if Timestamp value in the cache differs against Timestamp value in the DB, you’ll get the error we are talking about here.

Yes, there might be many honest reasons for this mismatching. Like, another user or another code has really done changes the same time. Or record has been created with mistake, and Acumatica may not find corresponding record in DB.

 

We propose you three approaches that can help fix concurrent update error and to allow running code logic till the end.

You may find two first approaches in source code of Acumatica Framework; Acumatica often uses them.

  1. To use SelectTimeStamp() graph’s method

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

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

		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();
			}
		}
And one more 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. To use PXTimeStampScope.SetRecordComesFirst() method

Sometimes first approach doesn’t help and the error still occurs.

This is because, by default, Acumatica checks TimeStamp only when key fields have been modified.

Calling to the SetRecordComesFirst() method activates RecordComesFirst flag and hereupon Acumatica checks TimeStamp value for all changes in cache.

Here is an 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<ARRegister> list = 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;
}

Usage by Acumatica team of this method in ARInvoiceEntry:

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

 3. To use PXTimeStampScope.DuplicatePersisted() method

This approach is the most difficult, but fixes the error when ordinary approaches don’t work.

There are no examples in Acumatica Framework source code. The next examples are my samples to override persist methods.

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));
        }
    }
}
And second example:
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));
        }
    }
}

 

In the second example described the approach usage in case when you need to persists dependent documents.

In both examples is also shown lazy graph initialization. This is not necessary to use it.
I use it for performance need, when I had long and heavy processing logic on my custom processing screen.

 Summary

Now you have needed knowledge and strategies on how to fix the "Another process has updated the {Table} record. Your changes will be lost" error in Acumatica, which occurs when multiple users try to modify the same record in the database. The error can be resolved by using the Timestamp field, which Acumatica uses to handle concurrent updates. The article proposes three approaches to fixing the error, including using the SelectTimeStamp() method, calling the SetRecordComesFirst() method, and using the DuplicatePersisted() method. The article provides code examples for each approach and explains how they can be used to fix the error.


 

Add comment

Loading