0

What is update_recordset in X++?

In Microsoft Dynamics 365 Finance & Operations (and earlier Dynamics AX versions), update_recordset is a set-based X++ statement used to update multiple records in a single operation. For a more general intro to the x++ programming language syntax IDE and more, you may want to check our what is x++ introduction guide first so you could a general idea what the x++ programming language even is and what it is usually used for in relation to both the general ideas of postfix versus prefix incrementation but more importantly the specifics about Microsoft Dynamics 365 Finance and Operations X++ Development using the programming language which is actually named X++ (pronounced X Plus Plus).

what is x++ update_recordset used for
The main difference between update_recordset and update is that you can use update_recordset to update more than one record in just one trip to your database!

So when it comes to x++ update_recordset vs update, well instead of fetching and updating each record one-by-one in a loop, an update_recordset translates to just a single SQL UPDATE query for you that operates on all records meeting the given criteria. This means it sends one request to the database to update all targeted rows at once, leveraging the SQL server’s capabilities for better performance. In essence, update_recordset in X++ is analogous to a SQL UPDATE ... SET ... WHERE ... statement executed on the database server side.

Because it is set-based, update_recordset can modify multiple rows and even multiple columns in one go. You specify the table to update, the field assignments (new values or expressions for one or more fields), and an optional where clause to filter which records should be updated. You can even use joins in an update_recordset to base the update on related data from other tables – a capability that is not available when updating records one by one using the standard method. For example, an official Microsoft example shows joining three tables in an update_recordset to set an employee status field based on data from Department and Project tables.

In summary, update_recordset is a powerful X++ construct for bulk updates: it allows you to update a set of records (potentially many rows) with new values in one database trip, rather than iterating through records in X++ code. This drastically reduces the communication between the application server and the database, which improves performance for large data operations.

How update_recordset Differs from the update() Method

The traditional update() method in X++ (called on a table buffer) updates one record at a time – specifically, the current record held in that buffer. In practice, using update() to modify multiple records means you must retrieve each record (e.g. in a loop or via separate selects) and call record.update() for each one. Each of those calls results in a database operation (or at least a round trip when the transaction is flushed), and X++ will handle them one-by-one. This row-by-row approach is straightforward but can be inefficient for large numbers of records, because it involves repeated context switching and multiple database calls. The example below illustrates a row-by-row update using the update() method:

// Example: Increase credit limit by 1000 for each customer (row-by-row)
CustTable cust;
ttsBegin;
while select forUpdate cust
    where cust.CreditLimit < 5000
{
    cust.CreditLimit += 1000;
    cust.update();   // updates one record at a time
}
ttsCommit;

In contrast, update_recordset performs a bulk update in one operation. There is no explicit loop in your X++ code; instead, the X++ runtime translates the statement into a single SQL update query that the database executes on all matching records. This set-based approach is much faster for large data sets, since it avoids the overhead of sending many individual update commands to the database. Using the same scenario as above, an update_recordset can achieve the update in one go:

// Example: Increase credit limit by 1000 for all customers with CreditLimit < 5000 (set-based)
CustTable cust;
ttsBegin;
update_recordset cust
    setting CreditLimit = cust.CreditLimit + 1000
    where cust.CreditLimit < 5000;
ttsCommit;

In this single statement, every customer record with CreditLimit under 5000 gets updated, all in one database trip. This highlights the core difference: update() updates the current record (one at a time), whereas update_recordset updates all records meeting the condition in one operation.

Another key difference involves business logic and system fields updates. The update() method may have overridden logic in the table’s code (for example, validations or custom computations in the table’s update() method or event handlers like onUpdating/onUpdated). When you call cust.update(), all such logic will execute for that record. With update_recordset, by default, X++ will skip calling per-record business logic on each row to gain performance – it directly issues an SQL update. If the table’s update() method is overridden with custom code, the runtime will actually fall back to record-by-record updates in order to respect that logic (essentially treating the update_recordset as if it were a loop calling update() on each record). In other words, certain conditions (like an overridden update method, database logging enabled on the table, alerts, etc.) will cause the set-based operation to downgrade to row-by-row mode to ensure no business rules are bypassed. This protects data integrity but also means you lose the performance benefit in those cases.

It is possible to explicitly override this safety fallback if you are sure it’s safe. X++ provides methods such as skipDataMethods(), skipDatabaseLog(), skipEvents(), etc., which you can call on the table buffer before an update_recordset to force skipping of custom update method logic, database logging, or event handlers respectively. For example, calling cust.skipDataMethods(true) before the update_recordset will prevent the system from invoking the overridden update() method on each record, ensuring the operation stays set-based. Use these with caution – they should only be used when you intentionally want to bypass those triggers or validations for performance reasons. In general, if you need the custom logic to run for each record, you might stick to using the standard update() in a loop; if not, update_recordset gives better speed.

To summarize the differences:

  • Scope of Update: The update() method operates on one record at a time (the record in the buffer), whereas update_recordset can update multiple records at once matching a query condition.
  • Performance: update_recordset is set-based and uses a single round-trip to the database for all records, making it significantly faster for bulk updates. The update() method involves multiple round-trips (one per record or per small batch) which is slower for large volumes.
  • Business Logic: The update() method will execute any overridden table logic and fire events for each record. update_recordset will not individually execute record-level logic by default, unless it has to fall back to row-by-row mode because such overrides or logging are present.
  • Syntax and Usage: update_recordset uses a declarative SQL-like syntax (setting Field = expression where condition), and can update several fields and even use joins in one statement. The update() method is an imperative call in X++ that requires you to handle record selection and looping explicitly in your code.

What is update_recordset Used For?

The primary use case for update_recordset is performing bulk updates efficiently. Whenever you need to update many records in a table – for example, applying a uniform change or formula to a large set of rows – update_recordset is typically the best tool. By updating multiple records in one database trip, it reduces network/database overhead and improves performance for mass updates. In scenarios such as data migration, periodic maintenance jobs, or large-scale adjustments to data (e.g. end-of-month routines, mass price updates), using update_recordset can make the operation complete much faster than looping with individual updates.

Typical situations where update_recordset is used include:

  • Mass attribute changes: e.g. setting a new status or default value on all records that meet certain criteria. Rather than iterating through each record, a single update_recordset can flip a status flag on thousands of records at once.
  • Bulk corrections or data fixes: e.g. increasing all inventory item prices by 10% for a category of products, or adjusting a numeric field across many records due to a business policy change.
  • Periodic batch jobs: In batch processes where lots of records need updates (for instance, applying interest to all savings accounts, or updating the next billing date for all active contracts), update_recordset helps achieve this in a set-based, efficient manner.
  • Improving legacy code performance: If an older code pattern is doing record-by-record updates in a loop and hitting performance limits, it can often be refactored to a single update_recordset statement (assuming the logic per record is simple enough to express in a set-based way). This can drastically shorten execution time.
  • Updating multiple fields together: You can set multiple columns in one update_recordset (using the setting Field1 = ..., Field2 = ... syntax), which is useful when a change involves multiple related fields. This not only saves additional trips but also ensures the fields are updated in one atomic operation for consistency.

One thing to note is that update_recordset works best when the update you need to perform is relatively uniform for all records (e.g. a formula or a fixed new value that can be calculated in SQL). If each record needs unique complex logic to determine the new value that cannot be expressed in a single SQL expression, you may not be able to use update_recordset directly and might resort to looping with update(). However, many common updates (adding a constant, multiplying by a factor, setting a specific enum value, etc.) are perfectly suited to the set-based approach.

In summary, update_recordset is used whenever you want to update many rows efficiently in Dynamics 365/AX and there is no need to execute custom business logic for each individual record (or you have explicitly chosen to skip that logic for performance). It’s a way to let the database do the heavy lifting in bulk, which is aligned with writing high-performance X++ code.

Examples of update_recordset in Action

To illustrate how update_recordset can be applied, let’s look at both a standard example and a few realistic scenarios in different industries. All examples assume the relevant tables and fields already exist in the Dynamics 365 F&O data model, and we focus only on the X++ update logic (within a transaction).

Standard Example – Increase Customer Credit Limits: A classic example (from Microsoft’s documentation) is increasing the credit limit for all customers who have a positive current limit. Using update_recordset, this can be done in one query, as shown below:

CustTable cust;
ttsBegin;
update_recordset cust
    setting CreditMax = cust.CreditMax + 1000
    where cust.CreditMax > 0;
ttsCommit;

In this example, every customer’s CreditMax field is incremented by 1000 if their current CreditMax is greater than 0. All qualifying records in CustTable are updated at once by the single statement inside the transaction. This contrasts with how one might have to do it with a loop and individual cust.update() calls without update_recordset. Similarly, you could update multiple columns in one go or base the update on a join (as discussed earlier), all within the same update_recordset statement.

Below are some real-world scenario examples in different business contexts, demonstrating how one might use update_recordset to perform bulk updates efficiently:

1. Electronics Manufacturer Scenario

Scenario: An electronics manufacturing company needs to increase the base price of all products in the “Accessories” category by 10%. Instead of updating each product one by one, they can use update_recordset to apply this price hike to all relevant products in one operation.

ProductTable prod;
ttsBegin;
update_recordset prod
    setting BasePrice = prod.BasePrice * 1.10
    where prod.Category == "Accessories" && prod.Active == NoYes::Yes;
ttsCommit;

Explanation: This X++ code finds all products in the ProductTable where the Category is “Accessories” and the product is marked as active, and increases each product’s BasePrice by 10%. The condition ensures inactive or other-category products are untouched. All matching product records are updated together within the single transaction. This is ideal for a mass price update scenario – it executes quickly and ensures consistency (all prices are updated to the new value range at the same time).

2. Real Estate Developer Scenario

Scenario: A real estate development firm wants to mark multiple properties as “Completed” in their system when construction has finished. For example, when a batch of units in a housing project reach their completion date, the status of those properties should be updated from “UnderConstruction” to “Completed”. Using update_recordset, this can be done for all finished properties at once.

PropertyTable property;
ttsBegin;
update_recordset property
    setting Status = PropertyStatus::Completed
    where property.Status == PropertyStatus::UnderConstruction 
      && property.CompletionDate <= today();
ttsCommit;

Explanation: This code updates the PropertyTable by setting the Status field to “Completed” for every property record that is currently labeled “UnderConstruction” and whose CompletionDate is today or in the past (meaning the construction is done). The use of update_recordset lets the developer update all such properties in one go, for example at the end of the day when several construction projects might complete. Without set-based updates, they might have to loop through each property or rely on manual updates, which is error-prone and slower.

3. Construction Company Scenario

Scenario: A construction company needs to adjust the budgets of all ongoing projects due to rising material costs. Suppose they decide to increase the budget by 5% for all projects that are still active and of type “Infrastructure”. Instead of updating each project individually, an update_recordset can handle it collectively.

ProjectTable proj;
ttsBegin;
update_recordset proj
    setting Budget = proj.Budget * 1.05
    where proj.Status == ProjectStatus::Active 
      && proj.ProjectType == ProjectType::Infrastructure;
ttsCommit;

Explanation: Here, every project in ProjectTable that has an Active status and is an Infrastructure project will get its Budget field multiplied by 1.05 (a 5% increase). This single statement applies the change to all qualifying project records. The transaction ensures that either all budgets are updated or none are if something goes wrong, maintaining data consistency. This kind of bulk update is useful for company-wide budget revisions and is much faster than iterating through each project record with a separate update.

4. International Investment Firm Scenario

Scenario: An international financial management firm wants to standardize interest rates for a certain type of account across all regions. For instance, they decide that all “Savings” accounts globally should have their interest rate set to 5%, overriding any previous rate differences. Using update_recordset, they can implement this policy change in one shot.

Account account;
ttsBegin;
update_recordset account
    setting InterestRate = 0.05
    where account.Type == AccountType::Savings;
ttsCommit;

Explanation: This example updates the Account table, setting the InterestRate field to 0.05 (5%) for every account that is of type “Savings”. In one query, all savings accounts in the system are updated to the new standard rate. This demonstrates the power of update_recordset for a financial scenario – a broad business rule change (like aligning interest rates) can be applied instantly to all affected records. There is no need to loop over accounts or handle them per region; the set-based update handles it globally in the context of the database. If some accounts had custom logic on update (for example, auditing changes), one would need to ensure that is handled appropriately (either allowed to trigger per record via fallback or consciously skipped if not needed).


Each of the above scenarios shows how update_recordset can simplify and accelerate what would otherwise be a repetitive update task. By using a set-based approach, the code is concise and clear in intent – it describes what change to make to which set of records, and leaves the heavy lifting to the database engine. This results in performance gains and more maintainable code for bulk updates. Just be mindful of the caveats: if there are complex business rules on the table, you may need to handle those (or let X++ revert to row-by-row mode), and always wrap such operations in ttsBegin/ttsCommit to maintain transactional integrity, as shown in the examples. Overall, understanding when to use update_recordset versus the standard update() method is an important part of writing efficient X++ code in Dynamics 365 Finance & Operations.

Have a Question ?

Fill out this short form, one of our Experts will contact you soon.

Call Us Today For Your Free Consultation