Thursday, October 13, 2011

Adding a new document Item whose metadata contains lookup fields using Client Object Model

It is easy to add new document item using client object model. In our case we are having all metadata corresponding to the item in a csv file and the csv file contains the look up filed value.
So we have the metadata including the lookup value, but whenever you try to execute the below code you will end up with an exception "Invalid data has been used to update the list item. The field you are trying to update may be read only".

I have written another blog post  related to this blog post that contains some more information
ClientContext ctx = new ClientContext(targetSiteUrl);
Web web = ctx.Web;
FileCreationInformation newFile = new FileCreationInformation();
newFile.Content = content;
newFile.Url = itemURL.Substring(itemURL.LastIndexOf("/") + 1);
List docs = web.Lists.GetByTitle(targetListName);
Microsoft.SharePoint.Client.File uploadFile = docs.RootFolder.Files.Add(newFile);
ListItem item = uploadFile.ListItemAllFields;
//Set the metadata     
item["Title"] = newFile.Url.Substring(0,newFile.Url.IndexOf("."));
item["Lookup Field"]=someLookupValue;  //this is the cause of the error.     

ContentType targetDocumentSetContentType = GetContentType(ctx,docs,contentType);
item["ContentTypeId"] = targetDocumentSetContentType.Id.ToString();

Then you don’t know what to do and which is causing the error. As shown in the below code, the highlighted line is causing the error. We can only set  FieldLookupValue
Object to the Fields of type Lookup Field. Instead of setting the FieldLookupValue if we set the text filed to it, it will throw the above error. So the solution is to set the  FieldLookupValue object to the lookup field.

The code follows below.
if (lookupField != null)
      mappedListItem = GetMappedListItem(lookupField, dataRow[field.Title].ToString());
if (mappedListItem != null)
     FieldLookupValue lookupValue = new FieldLookupValue();
     lookupValue.LookupId = mappedListItem.Id;
     item[field.InternalName] = lookupValue;

If you observe the above code, we are setting the LookupId only, we cannot set the LookupValue field as it is read only filed. But I have Lookup Value with me in the csv file. Then how can I get the ID value of the corresponding lookup value from the base list. The below code solves our problem.
Steps included in the code are  
  1.  Get the Web Object based on the LookupWebId present in the FieldLookup object.
  2.  Get the Look up list from the web based on the List ID present in the FieldLookup object.
  3.  Get the List item with its id based on the Lookup Value we have. 
 /// <summary>
        /// Gets corresponding Lookup field's base list item
        /// </summary>
        /// <param name="lookupField"></param>
        /// <param name="lookupValue"></param>
        /// <returns></returns>
        private ListItem GetMappedListItem(FieldLookup lookupField,string lookupValue)
            string mappingField = lookupField.LookupField;

            ClientContext mappedWebContext = new ClientContext(tbRootWebUrl.Text);

            Web web = mappedWebContext.Web;
            Web mappedWeb = GetLookupWeb(lookupField, web);
            if (mappedWeb == null)
                return null;
            mappedWebContext.Load(mappedWeb, p => p.Id, p => p.Title);
            List lookupList = mappedWeb.Lists.GetById(new Guid(lookupField.LookupList));

            ListItemCollection libListItems = lookupList.GetItems(CamlQuery.CreateAllItemsQuery());

            mappedWebContext.Load(libListItems, items => items.Include(
                    itemlookup => itemlookup.Id,
                    itemlookup => itemlookup[mappingField]));
            foreach (ListItem mappedItem in libListItems)
                if (mappedItem[mappingField].ToString() == lookupValue)
                    return mappedItem;
            return null;

        private Web GetLookupWeb(FieldLookup lookupField,Web web)
            ClientContext mappedWebContext = web.Context as ClientContext;

            Web mappedWeb = null;
            mappedWebContext.Load(web, webitm => webitm.Id, webitm => webitm.Title, webitm => webitm.ServerRelativeUrl);
            if (web.Id == lookupField.LookupWebId)
                return web;
            WebCollection webCollection = web.Webs;
            mappedWebContext.Load(webCollection, webitems => webitems.Include(itemweb => itemweb.Title, itemweb => itemweb.Id,itemweb=>itemweb.ServerRelativeUrl));
            foreach (Web webitem in webCollection)
                if (webitem.Id == lookupField.LookupWebId)
                    return webitem;
                if (mappedWeb != null)
            return mappedWeb;