One of the reasons, I prefer the client object model of SharePoint to the REST interface, is its capability of batching requests.
For example, you can add multiple users to a SharePoint group using the code below, and it is sent as a single request to the server:
- using (var clientContext = new ClientContext(url))
- {
- var web = clientContext.Web;
- var grp = web.SiteGroups.GetByName("YourGroup");
- var usersToAdd = new List<string>() { @"i:0#.w|domain\user1", @"i:0#.w|domain\user2" };
- foreach (var loginName in usersToAdd)
- {
- var user = web.EnsureUser(loginName);
- grp.Users.AddUser(user);
- }
- clientContext.ExecuteQuery();
- }
However, as the number of users you would like to add increases, you might have issues, as the operational requests in your batch are exceeding the 2 MB limit.
How could we solve the problem relative painless, avoiding the error, and still keeping our code readable?
The good news is that it is easy to achieve using extension method, generic, and the Action class. We can extend the ClientContext with an ExecuteQueryBatch method, and pass the list of parameter values to be processed in an IEnumerable, the action to be performed, and the count of items should be processed in a single batch. The method splits the parameter values into batches, calling the ExecuteQuery method on the ClientContext for each batch.
If the action, you would perform on the client objects has a single parameter (as in our case above, the login name is a single parameter of type String), the ExecuteQueryBatch method can be defined as:
- public static class Extensions
- {
- public static void ExecuteQueryBatch<T>(this ClientContext clientContext, IEnumerable<T> itemsToProcess, Action<T> action, int batchSize)
- {
- var counter = 1;
- foreach (var itemToProcess in itemsToProcess)
- {
- action(itemToProcess);
- counter++;
- if (counter > batchSize)
- {
- clientContext.ExecuteQuery();
- counter = 1;
- }
- }
- if (counter > 1)
- {
- clientContext.ExecuteQuery();
- }
- }
- }
Having the ExecuteQueryBatch method in this form, the original code can be modified:
- var batchSize = 20;
- using (var clientContext = new ClientContext(url))
- {
- var web = clientContext.Web;
- var grp = web.SiteGroups.GetByName("YourGroup");
- var usersToAdd = new List<string>() { @"i:0#.w|domain\user1", @"i:0#.w|domain\user2" /* and a lot of other logins */ };
- clientContext.ExecuteQueryBatch<string>(usersToAdd,
- new Action<string>(loginName =>
- {
- var user = web.EnsureUser(loginName);
- grp.Users.AddUser(user);
- }),
- batchSize);
- clientContext.ExecuteQuery();
- }
The size of batch you can use depends on the complexity of the action. For a complex action should be the batch smaller. The ideal value should you find experimentally.
Actions with multiple parameter require additional overloads of the the ExecuteQueryBatch extension method.
In my next post I’ll illustrate how to utilize this extension method in a practical example.