Don't use batch request types in plug-ins and workflow activities

Category: Usage, Reliability, Performance

Impact potential: Medium

Symptoms

User experiences are degraded and time out errors might occur when you use batch request types in plug-ins and workflow activities that occur within synchronous operations.

The following message request classes are considered batch request types because they perform operations on multiple records within a single request:

Guidance

Use these batch messages in client applications to perform operations on multiple records. Don't use these messages within code that Dataverse invokes during the execution of another operation: a plug-in or workflow activity registered for a synchronous step.

More specifically, use them in the following scenarios:

Problematic patterns

The following example shows using ExecuteMultipleRequest in the context of a plug-in.

Warning

This scenario should be avoided.

public class ExecuteMultipleRequestInPlugin : IPlugin
{
    public void Execute(IServiceProvider serviceProvider)
    {
        IPluginExecutionContext context = (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext));
        IOrganizationServiceFactory factory = (IOrganizationServiceFactory)serviceProvider.GetService(typeof(IOrganizationServiceFactory));
        IOrganizationService service = factory.CreateOrganizationService(context.UserId);

        QueryExpression query = new QueryExpression("account")
        {
            ColumnSet = new ColumnSet("accountname", "createdon"),
        };

        //Obtain the results of previous QueryExpression
        EntityCollection results = service.RetrieveMultiple(query);

        if (results != null && results.Entities != null && results.Entities.Count > 0)
        {
            ExecuteMultipleRequest batch = new ExecuteMultipleRequest();
            foreach (Entity account in results.Entities)
            {
                account.Attributes["accountname"] += "_UPDATED";

                UpdateRequest updateRequest = new UpdateRequest();
                updateRequest.Target = account;

                batch.Requests.Add(updateRequest);
            }

            service.Execute(batch);
        }
        else return;

    }
}

This example includes usage of the type directly with the Execute method. The usage can be anywhere within the context of a plug-in or workflow activity execution. This might be in a method that is contained within the same or a separate class, as well. It isn't limited to being directly contained in the Execute method definition.

Additional information

The purpose of the ExecuteMultiple message is to minimize round trips between client and server over high-latency connections. Plug-ins either execute directly within the application process or in close proximity when sandbox-isolated, meaning latency is rarely an issue. Plug-in code should be focused operations that execute quickly and minimize blocking to avoid exceeding timeout thresholds and ensure a responsive system for synchronous scenarios. Submit each request directly instead of batching them and submitting as a single request.

For example: foreach (request in requests) service.Execute(request)

On the server side, the operations included in a batch request are executed sequentially and aren't done in parallel. This is the case even if the ExecuteMultipleSettings.ReturnResponses property is set to false. Developers tend to use batch requests in this manner assuming that it allows for parallel processing. Batch requests don't accomplish this objective.

People use ExecuteTransactionRequest to ensure that each operation is included in a transaction. This is unnecessary within a synchronous plug-in step because the plug-in already being executed within the context of a database transaction, negating the need to use the ExecuteTransaction message.

See also

Event Framework
Run-time limitations
Execute multiple requests using the SDK for .NET
Execute messages in a single database transaction