acuamitca.com logo ACUAMITCA
~/blog/article

Persisting Data in Acumatica Events – Part 1

Acumatica ERP 11 mins read August 25, 2026
 

In Acumatica, data persistence within events is a critical concept that every developer must understand. The framework provides multiple ways to modify and persist data, each with its own use cases, advantages, and pitfalls. This multi-part series explores the different approaches to persisting data within Acumatica events.

⚡ Key Insight: The way you persist data in Acumatica events determines whether your customizations are upgrade-safe, performant, and free from infinite recursion issues.


The Foundation: Understanding PXCache

At the heart of Acumatica's data persistence mechanism is the PXCache object. Every data access class (DAC) in a graph has an associated cache that manages modified data records and controls operations like Insert(), Update(), and Delete() [citation:1].

The cache serves several critical functions:

  • Maintains Modified Records: It tracks which records have been inserted, updated, or deleted but not yet saved to the database
  • Raises Events: It triggers the appropriate sequence of events when data is modified
  • Manages Status: It tracks the status of each record using the PXEntryStatus enumeration

Record Statuses in PXCache

Every record in a cache has a status that determines how the framework will process it during the persist operation. The statuses are [citation:1]:

  • Notchanged: The record hasn't been modified since being retrieved
  • Updated: The record has been modified
  • Inserted: The record has been newly created
  • Deleted: The record has been marked for deletion
  • InsertedDeleted: A record that was inserted and then deleted within the same session

You can check a record's status by calling cache.GetStatus(row). To retrieve modified records, use collections like Cache.Inserted, Cache.Updated, Cache.Deleted, or Cache.Dirty (which returns all records with Inserted, Updated, or Deleted status) [citation:1].

💡 Pro Tip: Avoid iterating through the Cached collection directly. For performance optimization, this collection may not retrieve all available data records. Use the status-specific collections instead [citation:1].


Primary Data Modification Methods

The PXCache object provides three primary methods for modifying data [citation:1]:

1. Insert()

Creates a new record in the cache. When inserted, the record receives the Inserted status and will be persisted to the database when the graph's Persist() method is called.

var newOrder = new SOOrder();
newOrder.OrderNbr = "SO12345";
Orders.Insert(newOrder);

2. Update()

Modifies an existing record. The framework checks whether a record with the given key exists in the cache. If it exists, it is updated. If not, the framework retrieves the record from the database. If the record doesn't exist anywhere, the framework invokes Insert() instead [citation:1].

var order = Orders.SelectSingle();
order.Description = "Updated Description";
Orders.Update(order);

3. Delete()

Marks a record for deletion. The framework checks if the record exists in the cache. If not, it retrieves it from the database. If found, it sets the status to Deleted (or InsertedDeleted for newly inserted records) [citation:1].

var order = Orders.SelectSingle();
Orders.Delete(order);

The Persist() Method

The Persist() method of a graph is responsible for saving all modified records from all caches to the database. When you invoke Actions.PressSave() or the user clicks Save, the Persist() method is called [citation:5].

// Saving changes to the database
Base.Actions.PressSave();
// Or
Base.Persist();

The persistence process follows a specific order [citation:5]:

  1. A database transaction is opened
  2. For each modified record, the RowPersisting event is raised
  3. SQL commands are executed for all modified records
  4. The transaction is committed
  5. The RowPersisted event is raised with the transaction status

The Difference Between PressSave() and Persist()

While both methods ultimately call Persist(), Actions.PressSave() additionally [citation:5]:

  • Verifies that the Save action exists and is enabled in the graph
  • Is the recommended method for saving changes from the UI
  • Should not be invoked on the current graph instance from within a row event

⚠️ Important: You should not invoke Persist() on the current graph instance from within event handlers. Use Base.Actions.PressSave() instead, or consider using a graph created in a background operation [citation:5].


Understanding the Persistence Flow

The diagram below illustrates the sequence of events raised when changes are saved to the database [citation:3]:

Actions.PressSave() / Persist()
↓
Open Database Transaction
↓
For each modified record:
    RowPersisting → SQL Command → RowPersisted (TranStatus = Open)
↓
Commit/Abort Transaction
↓
RowPersisted (TranStatus = Completed/Aborted)

During the RowPersisting event, a database transaction is already open. If any handler sets e.Cancel to true, the process is canceled for that record without reporting an error. To cancel and report an error, you should throw a PXException [citation:3].

After the SQL commands are executed successfully, the transaction is committed. Regardless of the result, the RowPersisted event is raised with the transaction status: Completed or Aborted [citation:3].


When Events Are Raised

The framework raises events and updates record statuses in specific scenarios [citation:1]:

Scenario 1: Using Insert/Update/Delete

When you invoke Insert(), Update(), or Delete() on a cache:

  • Field-level events are raised for each field
  • Row-level events are raised for the data record
  • The record status is updated accordingly

Scenario 2: Using SetValueExt

When you invoke SetValueExt<Field>() on a cache:

  • Field-level events are raised for the specified field only
  • Row-level events are not invoked
  • The record status is not updated automatically

If you need to update the status, you must manually invoke SetStatus(). However, be cautious—changing the status manually may cause missing logic and incorrect data updates [citation:1].

Scenario 3: Direct Field Assignment or SetValue

When you assign a new value directly to a field or use SetValue():

  • No events are raised
  • The record status is not updated
// ❌ No events raised, status not updated
document.DocNbr = lastNumber;

// ❌ Also no events raised
view.Cache.SetValue(row, LastNumberField.Name, lastNumber);

// ✅ Events raised, status updated
Documents.Update(document);

Upcoming in Part 2

In the next part of this series, we'll explore:

  • Persisting within Different Events: RowPersisting vs RowPersisted
  • Using PXDatabase for Direct Updates: When and why to use it
  • Overriding Persist() and PerformPersist(): Advanced persistence control
  • Avoiding Infinite Recursion: Best practices for safe persistence

📌 Key Takeaway: Understanding PXCache and its data modification methods is the foundation for working with persistence in Acumatica events. The way you modify data determines which events are raised and whether your changes will be persisted correctly.

🔍 Further Reading: Review the Acumatica Developer Guide for detailed information on PXCache methods and event sequences. Understanding these concepts will make your customizations more reliable and maintainable.