Provisioning with the Client Object Model Part 2 – Getting Started and Provisioning Fields

Previous post: Provisioning with the Client Object Model Part 1 – Introduction

Until recently I thought of the client object model as a client side version of the server object model. Today I realize that thinking of CSOM this way is a mistake. The two models are very similar. However, the way you use them is very different. Because of this, differences that seemed very important when I looked did a direct comparison of the classes in the two API’s turned out to not be very important. For the most part this is because the way you create and access objects is fundamentally very different. This is not to say that CSOM doesn’t have any holes or couldn’t use some improvement. It does, it could, and I will point them out as I continue this series.

Thinking in batches and working in Layers

When you write provisioning code on the server you probably completely configure each object before you move on to the next. The following pseudo code illustrates what I mean.

For each list to create

    Create the list
    Bind the content type
    Configure the views

Next list

 

Because the code is running on the server, beyond the unavoidable expense of the operation itself, there is no penalty for persisting updates as you go. If you need pieces of information from some other object in the site, there is no penalty for accessing the object to get the information you need. For all practical purposes, in most situations such code is pretty straightforward and opportunities to make noticeable optimizations are few. I define the time to execute a potential operation on the server as:

[Time to perform server side operation]

When you are working on the client, every object access and every update is a potential round trip to the server and so it is easy to write inefficient code. The potential cost of a given server side operation becomes:

[Time to format request] + [Time to send request] + [Time to parse request] + 
[Time to perform server side operation] + [Time to format response] +
[Time to send response] + [Time to parse response]

Therefore the goal is to pay the expense of doing the server side operation as few times as possible for a given transaction. The client object model is designed to make this goal attainable by providing a few key features.

  • You can add operations to a batch of requests via ClientRuntimeContext.Load
  • You can submit a batch of operations with ClientRuntimeContext.ExecuteQuery
  • You can use an object without fetching it from the server it as long as you don’t try to read its properties

The flexibility of ClientRuntimeContext.Load and the ability to use an object without fetching it from the server are the things that make CSOM so fundamentally different from the server OM. It makes it possible to change the previous pseudo code to the following.

For each list to create
    Create the list

Next list

Submit the batch to the server

 

For each created list

    Bind the content type
Next list

Submit the batch to the server

 

For each created list

    Configure the views
Next list

Submit the batch to the server
			

 

The batches are determined by dependencies. A list must exist before a content type is added. A list must have all of its fields before configuring a view.

Provisioning Fields

The various Field classes were a significant source of concern for me when I compared the client and server API’s because there are a number of missing properties. It turns out that this is not an issue because you can manipulate all of a field’s settings via its SchemaXml property. The only wrinkle I have found is that there are a number of properties SharePoint wants you to set only when the field is in a List’s Fields collection, not in a Web or ContentType’s Fields collection. For this reason, the first step in provisioning the overall solution is to simply create, but not attempt to configure, each field. If a field is a lookup or metadata column it will require additional configuration, but I leave this until later in the overall process after the base list instances and/or term sets are created.

The easiest and best way to create a new field is via the FieldCollection.AddFieldAsXml Method. The XML this method takes as a parameter is exactly the same as the XML you would use to create the field via a feature. The primary thing that can go wrong when attempting this operation is an error if the field already exists. Because operations are submitted in batches the code must be defensive – you don’t want to be in a position where you must puzzle out which operation in a large batch is making the operation fail! Therefore the provisioning code must first get a list of the existing fields to prevent the error.

The pseudo code for the operation is simple.

Request the Web's Fields

Submit the batch to the server

For each field to create
    If the field does not exist

        Create the field from XML

Next field

Submit the batch to the server

 

As I said in the previous post, a primary goal is a good style that shares my favorite aspect of feature CAML, readability. For this reason, I declare the list of fields using literal syntax to create a Dictionary<string, string> where the key is the field’s StaticName and the value is its SchemaXml. The basic syntax is:

And a real example (cropped) looks like this:

The code to do the actual provisioning is simple.

Getting the Existing Fields

The check against the FieldDefinitions dictionary uses the value of the StaticName property and nothing else. Therefore the client should only request that property value when it requests the entire collection to minimize the message size. This is accomplished by the lambda expression passed as the second argument of the Load method as follows.

Note the call to ExecuteQuery. The FillExistingFieldsList method reads the StaticName property. Attempting to read StaticName before the call to ExecuteQuery will throw a PropertyOrFieldNotInitializedException. Attempting to refer to any other property of a Field after the call to ExecuteQuery will yield the same exception because the lambda expression in Load doesn’t include any other properties.

Optimizing the Query

One very powerful aspect of the lambda expression is that it can restrict the returned set by a property value. In my solution the fields are members of an App specific group or of hidden. I attempted to optimize the operation by modifying GetExistingFields as follows.

This ability is amazingly cool because it closes what I thought were a number of holes in CSOM related to fetching items. Examining the results in Fiddler shows that the response is ~33% smaller. However, it also shows that in this situation the call to ExecuteQuery took 53% longer than the unfiltered version. However, when the entire operation is timed, it the ‘optimized’ version is only 1.5% slower. Almost all of the time lost on the server was made up for on the client.

The clear take away is that you should not assume that reducing the response size via a query filter will result in better performance. However, minimizing the number of round trips is a safe bet. In my development environment I have 46 fields. If I changed the code to call ExecuteQuery after adding each field, the number of round trips increases from three to forty-eight and the overall time to complete increases by 82%. There is virtually no latency in my development environment, so in a production scenario where the App is hosted on a separate server, the difference would be dramatically greater.

Author: Doug Ware