Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 12

Atomic Data Operations with ExecuteTransactionRequest

Recommended resources

Hello! Welcome back to our advanced Power Platform developer course.

In our last lesson, we focused on boosting performance by processing large volumes of data with ExecuteMultipleRequest. We learned that its primary goal is throughput, and each operation within the batch runs independently in its own transaction.

Today, we're tackling the opposite requirement. What if you have a set of operations that must be treated as a single, indivisible unit? This lesson is all about ensuring data consistency. You will learn how to ensure data consistency across multiple operations using ExecuteTransactionRequest. This "all or nothing" principle is fundamental to building reliable business applications, and mastering it is a key skill for a senior developer.

1. From Performance to Consistency: The Need for Transactions

Imagine a common business scenario: when a new sales Order is created, you must also create several related Order Line records.

  • What happens if the Order record is created successfully, but the creation of one of the Order Line records fails due to invalid data?
  • You would be left with an incomplete order in your system—an orphaned Order header without its required details. This is a classic data integrity problem.

In these situations, you need to ensure that the entire sequence of operations either succeeds completely or fails completely, leaving the system untouched. This is the concept of a database transaction. Given your experience with SQL, you can think of this as the Dataverse equivalent of wrapping a series of SQL statements in a BEGIN TRANSACTION...COMMIT/ROLLBACK block.

ExecuteTransactionRequest is the message that brings this transactional capability to the Dataverse service layer.


2. Understanding ExecuteTransactionRequest

ExecuteTransactionRequest acts as a container for two or more OrganizationRequest messages. When you execute it, Dataverse wraps all the contained requests within a single database transaction. If any one of the requests fails, the entire transaction is automatically rolled back, and any data changes made by preceding requests in the batch are undone.

Let's look at the official documentation to see how this is structured.

Execute messages in a single database transaction

The Microsoft Learn documentation provides the definitive explanation of this message. It outlines its purpose and structure.

Please read the introduction and the main body of the article. Focus on: The 'all or nothing' principle. How the Requests collection is used. The effect of a single request failing on the entire transaction. The ExecuteTransactionFault that is returned on failure.

As the documentation highlights, the key points are:

  • Atomicity: All operations succeed, or none do.
  • Sequential Execution: Requests are executed in the order they appear in the Requests collection.
  • Rollback on Failure: If request #3 in a set of 5 fails, the changes made by requests #1 and #2 are automatically reverted.

3. Implementing a Transactional Operation in C#

The implementation pattern for ExecuteTransactionRequest will feel familiar after our last lesson, but the outcome is fundamentally different. Let's see it in action.

The following code example demonstrates creating multiple records. Let's imagine these are a primary record and its dependent children that must be created together.

How to execute messages within a single database transaction

This article from CRM Crate provides a clear, practical code example that shows both a successful transaction and, more importantly, a failed transaction that triggers a rollback.

First, review the C# code snippet that prepares and executes the ExecuteTransactionRequest. Then, pay close attention to the second part where the author intentionally causes a failure by passing an invalid OptionSet value. Compare the output of the successful run to the output of the failed run to see the rollback in action.

Code Structure and Failure Handling

As you saw in the examples, the core C# structure is:

  1. Create an OrganizationRequestCollection and populate it with the requests you want to execute transactionally (e.g., CreateRequest, UpdateRequest, DeleteRequest).
  2. Create an ExecuteTransactionRequest and assign your collection to its Requests property.
  3. Execute the request inside a try-catch block.
  4. Handle the success response by casting the result to ExecuteTransactionResponse and processing the Responses collection if needed.
  5. Handle failures by catching a FaultException<OrganizationServiceFault>.

When a transaction fails, the Detail property of the exception will be of type ExecuteTransactionFault. This is crucial for debugging. This fault object contains a property, FaultedRequestIndex, which tells you exactly which request in your collection (by its zero-based index) caused the failure.

// Example try-catch block for ExecuteTransactionRequest
try
{
    // Execute the request...
    var response = (ExecuteTransactionResponse)service.Execute(transactionRequest);

    // Process successful responses...
    Console.WriteLine("Transaction successful. All records were created.");
}
catch (FaultException<OrganizationServiceFault> ex)
{
    // Check if the fault is from our transaction
    if (ex.Detail is ExecuteTransactionFault transactionFault)
    {
        // Get the index of the request that failed
        int failedRequestIndex = transactionFault.FaultedRequestIndex;
        string failedRequestName = transactionRequest.Requests[failedRequestIndex].RequestName;

        Console.WriteLine($"Transaction rolled back. The operation failed at request #{failedRequestIndex + 1} ({failedRequestName}).");
        Console.WriteLine($"Reason: {ex.Detail.Message}");
    }
    else
    {
        // Handle other service faults
        Console.WriteLine($"An unexpected error occurred: {ex.Message}");
    }
}
Test your understanding!

You are designing a process to create a new Invoice record and its related Invoice Line records in Dataverse. It is critical that an invoice is never created without its corresponding lines. If any of the Invoice Line records fail to be created, the parent Invoice record should also not be created.

Which message should you use (ExecuteTransactionRequest or ExecuteMultipleRequest) and why?

Show answer

You should use ExecuteTransactionRequest.

The key requirement is "all or nothing" data consistency. If the creation of any invoice line fails, the entire operation, including the creation of the parent invoice, must be rolled back to prevent orphaned data. ExecuteMultipleRequest would not provide this guarantee; it could successfully create the invoice and some lines while others fail, leaving the data in an inconsistent state.


4. ExecuteTransactionRequest vs. ExecuteMultipleRequest: A Summary

Choosing between these two messages is a critical architectural decision that frequently appears in PL-400 scenarios. The choice depends entirely on your primary goal: consistency or performance.

Feature ExecuteTransactionRequest ExecuteMultipleRequest
Primary Goal Data Consistency (Atomicity) Performance (Throughput)
Transaction Scope Single database transaction for all requests Each request runs in its own transaction
Failure Behavior "All or nothing". One failure rolls back everything. Operations are independent. ContinueOnError controls if the batch stops.
Typical Use Case Multi-step operations that must complete as a single unit (e.g., financial postings, creating a header and its required lines). Bulk data migrations, integrations where records are not dependent on each other.
Dataverse Operations with Partial Failure
This image illustrates what happens with `ExecuteMultipleRequest` when `ContinueOnError` is true: one operation fails, but the subsequent ones succeed. Had this been an `ExecuteTransactionRequest`, none of the records would have been created; the system state would have been rolled back upon the first failure, ensuring perfect data consistency.

A final important constraint: you cannot nest these requests. An ExecuteTransactionRequest cannot contain an ExecuteMultipleRequest or another ExecuteTransactionRequest.


Conclusion

In this lesson, you've learned how to enforce strict data consistency across multiple Dataverse operations. By leveraging ExecuteTransactionRequest, you can group a series of actions into a single atomic transaction, guaranteeing that your data is never left in a partially completed or invalid state.

Key Takeaways:

  • ExecuteTransactionRequest ensures that a collection of operations either all succeed or all fail together.
  • If any operation within the transaction fails, all previous operations in that transaction are automatically rolled back.
  • This is the ideal pattern for scenarios requiring high data integrity, such as creating a header record with its required child records.
  • Use the ExecuteTransactionFault object to identify which specific operation caused the transaction to fail.
  • Choose ExecuteTransactionRequest for consistency and ExecuteMultipleRequest for performance.

Up Next:
We have now explored how to manage multiple operations at the service level for both performance and consistency. In the next lesson, we will shift our focus to error handling within the scope of a single operation. We will learn how to handle exceptions and implement retry logic in plugin development, giving you the tools to build more resilient and robust server-side code.