Hello! Welcome back to our advanced Power Platform developer course.
In the last lesson, we learned how to create our own custom APIs, defining new, reusable operations in Dataverse. This is perfect for single, well-defined business actions. But what happens when you need to perform thousands of operations at once, such as during a large data migration or a nightly integration job? Calling the service one operation at a time would be incredibly inefficient.
This lesson directly addresses that challenge. We will cover how to implement batch operations to improve performance using ExecuteMultipleRequest. For a developer with your extensive C# and server-side experience, mastering this pattern is key to building scalable, high-throughput applications on the Dataverse platform.
1. The Problem: The Cost of Sequential Operations
Imagine you have a list of 5,000 contact records to create or update. Your most straightforward approach, using the IOrganizationService interface you're familiar with, would be to loop through the collection and call the service for each record.
// The naive approach
foreach (var entity in collectionOfEntitiesToUpdate)
{
// Each call is a separate network round-trip
service.Update(entity);
}
While this works, it's slow. Each Update call is a separate HTTP request to the Dataverse server. The total execution time is dominated not by the server's processing, but by the cumulative network latency of thousands of round-trips.
Improving Insert/Update/Delete Performance in D365/Dataverse
To see a practical look at this performance problem, please read the first two sections of this excellent blog post by Mark Carrington, a well-known developer in the community.
Read the sections 'Simple approach: Single updates' and 'ExecuteMultipleRequest'. The author provides concrete numbers showing how batching cuts execution time in half simply by reducing network overhead.
2. The Solution: Batching with ExecuteMultipleRequest
The ExecuteMultipleRequest message is the classic solution to this problem. It acts as a container, allowing you to bundle a collection of individual OrganizationRequest objects (like CreateRequest, UpdateRequest, DeleteRequest, or even calls to your custom APIs) and send them to the server in a single network call.
The server unpacks the batch and executes each request sequentially. This dramatically improves throughput, especially in high-latency environments.
Let's dive into the official documentation to understand the structure of this request.
Execute multiple requests using the SDK for .NET
The Microsoft Learn documentation provides the definitive guide to using ExecuteMultipleRequest. This will cover its structure, settings, and how to process the results.
Please read the introduction and the 'Example' section. Pay close attention to two key points: How the OrganizationRequestCollection is populated with different requests. The statement that 'Each message request in the input collection is processed in a separate database transaction.' This is a critical distinction from what we'll cover in the next lesson.
Core Implementation
As you saw in the documentation, the core pattern involves three steps:
- Construct the Request: Create an
ExecuteMultipleRequestobject, populate itsRequestscollection, and configure itsSettings. - Execute the Batch: Call
service.Execute()with your batch request. - Process the Response: Loop through the
ExecuteMultipleResponse.Responsescollection to check for the success or failure of each individual operation.
Here's the essential code structure from the Microsoft example:
// 1. Construct the request
var executeMultipleRequest = new ExecuteMultipleRequest()
{
Settings = new ExecuteMultipleSettings()
{
ContinueOnError = true, // Or false
ReturnResponses = true // Or false
},
Requests = new OrganizationRequestCollection()
};
// Add individual requests to the batch
foreach (var entityToCreate in myEntityCollection.Entities)
{
CreateRequest createRequest = new CreateRequest { Target = entityToCreate };
executeMultipleRequest.Requests.Add(createRequest);
}
// 2. Execute the batch
var executeMultipleResponse =
(ExecuteMultipleResponse)service.Execute(executeMultipleRequest);
// 3. Process the response
foreach (var responseItem in executeMultipleResponse.Responses)
{
if (responseItem.Fault != null)
{
// A fault occurred for this specific request
// You can get the original request index via responseItem.RequestIndex
}
else
{
// This request was successful
}
}
3. Controlling Execution and Handling Failures
The real power and flexibility of ExecuteMultipleRequest come from the ExecuteMultipleSettings. These settings are crucial for building robust data integration processes.
ExecuteMultipleSettings Member |
Description | Practical Implication |
|---|---|---|
ContinueOnError |
If true, the server continues processing the remaining requests in the batch even if one fails. If false, it stops at the first failure. |
Set to true for bulk data loads where a few bad rows shouldn't stop the entire job. Set to false when the sequence is important and an early failure invalidates subsequent steps. |
ReturnResponses |
If true, the Responses collection contains an item for every processed request (either a Response object or null). If false, it only contains items for requests that produced a Fault. |
Set to true when you need the new IDs from Create operations or other specific return values. Set to false for "fire and forget" updates or deletes to minimize the size of the response payload. |
The combination of these settings determines the contents of the response you get back, which is a common topic for exam questions.
Execute multiple requests using the SDK for .NET
To solidify your understanding of these settings, review the 'Specify run-time execution options' section in the Microsoft documentation we looked at earlier.
Focus on the table that shows the contents of the Responses collection for different combinations of ContinueOnError and ReturnResponses. This will clarify how to interpret the results of your batch operations.
4. Real-World Best Practices
In a production scenario, you can't just throw 50,000 requests into a single ExecuteMultipleRequest. You need to handle server-side limits and process the work in manageable chunks.
Batching and Throttling
Dataverse imposes a limit on the number of requests you can include in a single ExecuteMultipleRequest batch (typically 1000). Exceeding this limit will cause the entire batch to fail before it even starts.
The standard professional practice is to break your large collection of operations into smaller "mini-batches" and execute them sequentially or in parallel.

Handling the MaxBatchSize Fault
What if the server's batch size limit changes? A robust application shouldn't hard-code the limit. Instead, you can wrap your Execute call in a try-catch block to specifically handle the fault that occurs when the batch size is exceeded. The server helpfully includes the MaxBatchSize in the fault details, allowing your code to adjust dynamically.
Execute multiple requests using the SDK for .NET
The Microsoft documentation provides a code snippet for exactly this scenario. It's a hallmark of professional-grade code.
Read the sections 'Run-time limitations' and 'Handle a batch size fault'. This covers the batch size limit and shows the C# code for catching the specific FaultException and extracting the MaxBatchSize value.
Test your understanding!
You are writing a data migration utility to import 10,000 records from a legacy system into Dataverse. You must log the new GUID for each successfully created record. However, you anticipate that about 1-2% of the source records may be malformed and will fail validation upon creation. The failure of one record should not prevent the others from being created.
Which ExecuteMultipleSettings would you use and why?
Show answer
ContinueOnError = true: This is essential. The requirement is that the failure of a few records should not stop the entire import process.ReturnResponses = true: This is also necessary. You need to log the new GUID for each successfully created record. The GUID is returned in theCreateResponseobject, which will only be present in theResponsescollection if this setting istrue.
5. Important Caveats and Modern Alternatives
Do Not Use in Synchronous Plugins
A critical rule for PL-400 and real-world development is to avoid using ExecuteMultipleRequest inside a synchronous plugin. Doing so can easily exceed the 2-minute plugin timeout and creates complex transaction rollback scenarios. This is considered a bad practice and will be flagged by the Solution Checker.
Modern, Specialized Alternatives
In recent years, Microsoft has introduced more specialized and even more performant messages for specific bulk operations: CreateMultipleRequest and UpdateMultipleRequest.

- Use
CreateMultiple/UpdateMultiplewhen all your operations are of the same type (all creates or all updates). They offer better performance for these specific scenarios. - Use
ExecuteMultiplewhen you need to mix different types of operations (e.g., creating some records, updating others, and deleting a few, all in the same batch) or when calling actions/custom APIs.
Conclusion
You have now learned the cornerstone pattern for performing bulk data operations in Dataverse. By batching requests with ExecuteMultipleRequest, you can build highly performant and scalable integrations and data management tools.
Key Takeaways:
ExecuteMultipleRequestdramatically improves performance by reducing network round-trips.- Each operation within a batch runs in its own separate transaction. It is not an "all-or-nothing" operation.
- The
ContinueOnErrorandReturnResponsessettings give you fine-grained control over execution flow and result handling. - Always break large workloads into smaller batches to stay under the server's
MaxBatchSizelimit. - Never use
ExecuteMultipleRequestin a synchronous plugin. - For pure create or update scenarios, consider the newer
CreateMultipleRequestandUpdateMultipleRequestfor even better performance.
Up Next:
We've focused entirely on performance, where each part of a batch succeeds or fails independently. But what if you need to guarantee that a series of operations either all succeed or all fail together, as a single atomic unit? In our next lesson, we will explore the other side of this coin: ExecuteTransactionRequest, which is designed for data consistency rather than raw throughput.