10061 Views // 0 Comments // Not Rated

SharePoint 2013 Quick Tips: Using CSOM To Update A ListItem Results In Empty Fields

I'm working on an app that publishes content from one farm to another - all in CSOM. One cool thing about SharePoint's C# client API is that it allows us to easily simulate cross-farm functionality on our local (assumedly web garden) development environment. CSOM doesn't care whether it's context is a remote site collection, web application, or farm; everything in CSOM is technically remote. So as long as we're spanning site collections (and our service applications are configured properly for whatever scenario we're testing) our code is already pretty far down the path of being "farm safe."

The specific scenario I encountered here was around updating a ListItem with a dynamic set of columns. All the logic to make sure I had the correct field internal names and the correct ClientObjects being loaded from the correct ClientContexts wasn't the issue. The issue was that random fields weren't taking their new values! Here's what my poor, sad, unfortunate Immediate window looked like:





targetItem["target_x0020_colum"] = localItem["local_x0020_colum"]




[run the rest of the ListItem population logic]

[set and hit a breakpoint located just before the Update and ExecuteQuery calls]




Expression has been evaluated and has no value


Expression has been evaluated and has no value




Syntax error, ']' expected

shut up

; expected

To make a long, dumb, weekend-crushing story short, the issue wasn't bad column names, uninitialized properties, or ClientContext.ExecuteQuery omissions. As I debugged real hard into the [run the rest of the ListItem population logic] I noticed that when that code called ExecuteQuery against the same context that owned my ListItem, all current field values were wiped out. This makes sense, because the Update call never happened.

This other logic was doing things like copying pictures for image columns and looking up taxonomy values. I assumed that this would be okay because I never called Update to commit the values. ExecuteQuery should therefore ignore my changes, and as long as I didn't rehydrate anything, I should be able to continue populating my ListItem, call Update when I'm done, and then execute the query to save everything.

Nope...not at all. Calling ExecuteQuery against a ClientContext that contains a ListItem that hasn't had its Update method called puts that item into an indeterminate state. I didn't go so far as to look at the request to see what was getting sent over the wire, because I found that calling ListItem.Update and ClientContext.ExecuteQuery after every field was set worked. Then I found that not calling my special case field logic worked as well.

The common theme here? Every Update has a corresponding ExecuteQuery. So what I did to solve the problem was spin up a new ClientContext for my "intermediate" field population logic so that my current context and item remained clean. Cloning a ClientContext is pretty straightforward, and as long as it's not done too liberally, shouldn't have an impact on performance. Here's what that code looks like:

Code Listing 1

  1. public static ClientContext Clone(this ClientContext context)
  2. {
  3. //open a new context
  4. ClientContext newContext = new ClientContext(context.Url)
  5. {
  6. //assemble object
  7. Credentials = context.Credentials,
  8. RequestTimeout = context.RequestTimeout
  9. };
  10. //load root web
  11. newContext.LoadWeb(newContext.Web);
  12. //return
  13. return newContext;
  14. }

First, in Line #1, as you'll see in many of my upcoming posts, we're in an extension method that hangs off of ClientContext. I like this style of CSOM programming because it gives us access to everything we might need in our current SharePoint site. Next we use the same url to new up the clone, and pass through the credentials and request timeout.

Starting on Line #11, we can do any initialization logic needed. In the example here, LoadWeb requests several properties and collections on the ClientContext's (and therefore site collection's) root Web. Finally, make sure to dispose of the return value of this extension method; treat SharePoint ClientContexts as you would Entity Framework's data contexts.

using (ClientContext newContext = currentContext.Clone()) {...}

The moral of the story is to not call ClientContext.ExecuteQuery when there is a ListItem that's being populated; fill in all fields first and then call ListItem.Update. This might seem trivial when your code is setting a handful of static columns. But if you have a few nested calls of helper extension methods hanging off a ClientContext and dynamic field population logic with anonymous delegates, it not be so clear as to what's going on. There are a million other reasons why your ListItems might not get their data; hopefully this situation will not be one of them any longer!

No Tags

No Files

No Thoughts

Your Thoughts?

You need to login with Twitter to share a Thought on this post.