Skip to main content
Question

Writing a history row to a custom table from a custom DAC field attribute (2026 R1)

  • September 29, 2026
  • 4 replies
  • 52 views

We're on 2026 R1 and building field-change history for time-in-stage reporting, starting with CRLead.Status and CROpportunity.StageID. Each change should add a row to a custom table, BXFieldHistory (entity NoteID, field name, old value, new value, changed by, changed on).

Updates come from several places: the stock screens, Lead → Opportunity conversion, mass-update screens, and an external integration using the contract-based REST API. So we plan to attach a custom attribute (a PXEventSubscriberAttribute subclass) to the fields through a DAC extension with PXMergeAttributes, rather than one graph extension per screen.

public class BXTrackHistoryAttribute : PXEventSubscriberAttribute,
IPXRowUpdatedSubscriber, IPXRowPersistedSubscriber
{
// RowUpdated: compare e.Row vs e.OldRow, remember the change
// RowPersisted: write the BXFieldHistory row
}

What's the supported way to write the BXFieldHistory row from inside the attribute, so it commits or rolls back with the parent record in every graph that uses the DAC?

  1. Insert into sender.Graph.Caches[typeof(BXFieldHistory)] during RowUpdated/RowPersisting so the graph persists it. Will a graph reliably persist a cache for a DAC it has no view for?
  2. PXDatabase.Insert<BXFieldHistory>(...) in RowPersisted while e.TranStatus == PXTranStatus.Open. Is a direct insert there acceptable for a plain log table?
  3. Some other pattern, or a stock attribute that already does this that we could model on.

Also, are there any stock processes that update these fields without raising cache events (for example PXDatabase.Update), which would bypass the attribute?

Thanks!

4 replies

darylbowman
Captain II
Forum|alt.badge.img+17

If I were doing it, I believe I would go with #2.

Is a direct insert there acceptable for a plain log table?

 

I believe this is exactly the types of situations direct database edits were designed for.


harutyungevorgyan
Jr Varsity I
Forum|alt.badge.img+4

Hi ​@akulberg ,

I completely agree with ​@darylbowman. If you are building a custom log table, using PXDatabase.Insert<BXFieldHistory>(...) inside the RowPersisted event (specifically when e.TranStatus == PXTranStatus.Open) is the safest and most performant route. It bypasses the graph cache overhead entirely while ensuring your history record strictly commits or rolls back alongside the main transaction.

Before writing the custom attribute and DAC from scratch, consider evaluating Acumatica's native Field-Level Audit feature. The stock framework already captures old versus new values, timestamps, and modifying user IDs for any configured field out of the box, which might fulfill your time-in-stage reporting requirements without the need to maintain a custom table.


Forum|alt.badge.img+3
  • Jr Varsity III
  • September 30, 2026

​@akulberg ,
   Key on the entity NoteID, read via sender.GetValue(e.Row, nameof(CROpportunity.NoteID)) or PXNoteAttribute.GetNoteID.sender.GetValue(e.Row, nameof(CROpportunity.NoteID)) or PXNoteAttribute.GetNoteID.
can you try like this.
 

public void RowPersisted(PXCache sender, PXRowPersistedEventArgs e)
{
    if (e.TranStatus != PXTranStatus.Open) return;
    if ((e.Operation & PXDBOperation.Command) != PXDBOperation.Update) return;

    object oldVal = sender.GetValueOriginal(e.Row, _FieldName);
    object newVal = sender.GetValue(e.Row, _FieldName);
    if (Equals(oldVal, newVal)) return;

    PXDatabase.Insert<BXFieldHistory>(
        new PXDataFieldAssign<BXFieldHistory.entityNoteID>(sender.GetValue(e.Row, "NoteID")),
        new PXDataFieldAssign<BXFieldHistory.fieldName>(_FieldName),
        new PXDataFieldAssign<BXFieldHistory.oldValue>(oldVal?.ToString()),
        new PXDataFieldAssign<BXFieldHistory.newValue>(newVal?.ToString()),
        new PXDataFieldAssign<BXFieldHistory.changedByID>(PXAccess.GetUserID()),
        new PXDataFieldAssign<BXFieldHistory.changedOn>(PXTimeZoneInfo.UtcNow));
}


  • Author
  • Freshman I
  • October 1, 2026

Hi ​@darylbowman , ​@harutyungevorgyan , ​@noorula77 , thank you all for your speedy responses. I am so appreciative of this community!

​@harutyungevorgyan , we’ve spent the last couple of days re-evaluating the OOTB field-level audit based on your suggestion, but it has too many limitations for what we need.

I’ll report back here with a ‘Hey we got it working’ and any interesting findings along the way once we’re up and running.

Thanks again for all three of your support!