Search code examples
c#sharepointcsomsharepoint-2016

SharePoint CSOM not throwing expected exceptions on ExecuteQuery


Essentially what the title says, for whatever reason when I call clientContext.ExecuteQuery(); and for example the list the data is going into has a missing column, I don't get an exception, it actually just runs as expected and I have to go and investigate what might have caused the data to not appear.

The NuGet package i'm using is Microsoft.SharePoint2016.CSOM version 16.0.4690.1000

Any hints, or suggestions to point me in the right direction appreciated. It's entirely possible I'm being a bit dim here.

Here's the full code block I'm using for updating list items:

public override object UpdateEntity(object entity)
{
    if (entity == null)
    {
        // if the definition is null throw argument null exception.
        throw new ArgumentNullException(nameof(SharePointDefinition));
    }

    // check for incorrect type being passed in that we can still handle
    if (entity is List<SharePointDefinition> definitions)
    {
        return UpdateEntities(definitions);
    }

    ExceptionHandlingScope exceptionScopeFetch = new ExceptionHandlingScope(clientContext);
    ExceptionHandlingScope exceptionScopeSubmit = new ExceptionHandlingScope(clientContext);

    // run single definition submit to SP.
    if (entity is SharePointDefinition definition)
    {
        // variables.
        IntegrationEventLog log = new IntegrationEventLog();
        EventInformation ei = new EventInformation();
        List list = null;
        ListItemCollection listItemCol = null;

        using (exceptionScopeFetch.StartScope())
        {
            using (exceptionScopeFetch.StartTry())
            {
                // get the required list
                list = clientContext.Web.Lists.GetByTitle(definition.ListName);

                // create query
                listItemCol = list.GetItems(CamlQuery.CreateAllItemsQuery());

                // set these items for retrieval.
                clientContext.Load(listItemCol);
            }

            using (exceptionScopeFetch.StartCatch())
            {
                // Assume that if there's an exception, it can only be 
                // because there is no list with the specified title, so report this back.
                if (exceptionScopeFetch.HasException)
                {
                    ei = new EventInformation()
                    {
                        LoggingEventType = LoggingEventType.DataSentFailure,
                        LoggingSeverity = LoggingSeverity.HighSeverity,
                        SerialisedMessage = JsonConvert.SerializeObject(definition),
                        ServiceMessage = $"Hit SharePoint exception handler during list and data pull: Message: {exceptionScopeFetch.ErrorMessage}",
                        StackTrace = exceptionScopeFetch.ServerStackTrace,
                        TimeGenerated = DateTime.Now,
                        TimeWritten = DateTime.Now,
                    };

                    log.EventLogEntryType = EventLogEntryType.Error;
                    log.EventID = LoggingSeverity.HighSeverity;
                    log.Message = JsonConvert.SerializeObject(ei);
                    AddToLog(log);
                }
            }

            using (exceptionScopeFetch.StartFinally())
            {
                //
            }
        }

        // get item instances first
        try
        {
            clientContext.ExecuteQuery();
        }
        catch (Exception genEx)
        {
            // return failure log.
            ei = new EventInformation()
            {
                LoggingEventType = LoggingEventType.DataSentFailure,
                LoggingSeverity = LoggingSeverity.HighSeverity,
                SerialisedMessage = JsonConvert.SerializeObject(definition),
                ServiceMessage = "Errored trying to get data from SharePoint list ready for update operation; see stacktrace for more information.",
                StackTrace = genEx.StackTrace,
                TimeGenerated = DateTime.Now,
                TimeWritten = DateTime.Now,
            };

            log.EventLogEntryType = EventLogEntryType.Error;
            log.EventID = LoggingSeverity.HighSeverity;
            log.Message = JsonConvert.SerializeObject(ei);
            AddToLog(log);

            return false;
        }

        // this is the column we want to overwrite.
        var comparisonColumn = definition.UpdateIdentifier ?? "";

        //List col to dict
        var listItems = listItemCol.Cast<ListItem>().ToList();

        // Now we know if we were able to retrieve existing data, perform submit.
        using (exceptionScopeSubmit.StartScope())
        {
            using (exceptionScopeSubmit.StartTry())
            {
                // loop through our rows
                foreach (var row in definition.RowData)
                {
                    int existingItemIndex = -1;
                    // see if the row exists already
                    if (!string.IsNullOrEmpty(comparisonColumn) && listItems.Count != 0)
                    {
                        existingItemIndex = listItems.FindIndex(x => x[comparisonColumn].ToString() == row[comparisonColumn]);
                    }

                    if (existingItemIndex != -1 && listItems.Count != 0)
                    {
                        // item exists - loop through our row columns
                        foreach (var keyValuePair in row)
                        {
                            // they key relates to a column, the Value to the rows colum Value.
                            listItems[existingItemIndex].ParseAndSetFieldValue(keyValuePair.Key, keyValuePair.Value);
                        }

                        // update this item
                        listItems[existingItemIndex].Update();
                    }
                    else
                    {
                        ListItemCreationInformation itemCreateInfo = new ListItemCreationInformation();
                        ListItem newItem = list.AddItem(itemCreateInfo);

                        // loop through our row columns
                        foreach (var keyValuePair in row)
                        {
                            // they key relates to a column, the Value to the rows colum Value.
                            newItem[keyValuePair.Key] = keyValuePair.Value;
                        }
                        newItem.Update();
                    }
                }
            }

            using (exceptionScopeSubmit.StartCatch())
            {
                // Assume that if there's an exception, it can only be 
                // because there is no list with the specified title, so report this back.
                if (exceptionScopeFetch.HasException)
                {
                    ei = new EventInformation()
                    {
                        LoggingEventType = LoggingEventType.DataSentFailure,
                        LoggingSeverity = LoggingSeverity.HighSeverity,
                        SerialisedMessage = JsonConvert.SerializeObject(definition),
                        ServiceMessage = $"Error at SharePoint exception handler during submit to list: Message: {exceptionScopeFetch.ErrorMessage}",
                        StackTrace = exceptionScopeFetch.ServerStackTrace,
                        TimeGenerated = DateTime.Now,
                        TimeWritten = DateTime.Now,
                    };

                    log.EventLogEntryType = EventLogEntryType.Error;
                    log.EventID = LoggingSeverity.HighSeverity;
                    log.Message = JsonConvert.SerializeObject(ei);
                    AddToLog(log);
                }
            }

            using (exceptionScopeSubmit.StartFinally())
            {
                //
            }
        }

        // try to execute submit.
        try
        {
            clientContext.ExecuteQuery();

            ei = new EventInformation()
            {
                LoggingEventType = LoggingEventType.DataSentSuccess,
                LoggingSeverity = LoggingSeverity.Information,
                SerialisedMessage = JsonConvert.SerializeObject(definition),
                ServiceMessage = "No exceptions were thrown from the Execution process.",
                StackTrace = "",
                TimeGenerated = DateTime.Now,
                TimeWritten = DateTime.Now,
            };

            log.EventLogEntryType = EventLogEntryType.Information;
            log.EventID = LoggingSeverity.Information;
            log.Message = JsonConvert.SerializeObject(ei);
        }
        catch (Exception genEx)
        {
            ei = new EventInformation()
            {
                LoggingEventType = LoggingEventType.DataSentFailure,
                LoggingSeverity = LoggingSeverity.HighSeverity,
                SerialisedMessage = JsonConvert.SerializeObject(definition),
                ServiceMessage = $"Data failed to be updated within SharePoint - {genEx.Message} - see stacktrace for more information.",
                StackTrace = genEx.StackTrace,
                TimeGenerated = DateTime.Now,
                TimeWritten = DateTime.Now,
            };

            log.EventLogEntryType = EventLogEntryType.Error;
            log.EventID = LoggingSeverity.HighSeverity;
            log.Message = JsonConvert.SerializeObject(ei);
            AddToLog(log);

            return false;
        }

        AddToLog(log);
        return true;
    }
    else
    {
        // If a different definition type is passed in throw an appropriate exception. 
        // This should be caught at runtime only.
        throw new TypeLoadException(nameof(SharePointDefinition));
    }
}

Solution

  • So I went away and re-read a lot of documentation and looked over some examples, and I wondered why the exception handler never returned errors even though it's obvious there were issues with the query being executed. If you look at the Microsoft documentation and their example here you'll see that they don't show you how to use exceptionScopeSubmit.HasException component of the exception scope object. Which lead to a really stupid assumption on my part. It turns out that the exception block runs on the SharePoint server and should be used to fix issues you might have caused during an expected exception in your query.

    Not only that but wrapping the ExecuteQuery in the try catch is redundant when using an exception scope as it means that method will no longer throw an exception. You actually have to assess exceptionScopeSubmit.HasException after the execution to pull back some more detailing information on errors reported by the SharePoint server side execution of your query.

    So now I'm using it as below, and I can get detailed error information without having to do some stupid manual debugging which would easily take me hours to track down silly issues. So in case anyone stumbles across this having the same issues, I hope it helps.

    ExceptionHandlingScope exceptionScopeFetch = new ExceptionHandlingScope(clientContext);
    
    // variables.
    IntegrationEventLog log = new IntegrationEventLog();
    EventInformation ei = new EventInformation();
    List list = null;
    ListItemCollection listItemCol = null;
    
    using (exceptionScopeFetch.StartScope())
    {
        using (exceptionScopeFetch.StartTry())
        {
            // get the required list
            list = clientContext.Web.Lists.GetByTitle(definition.ListName);
    
            // create query
            listItemCol = list.GetItems(CamlQuery.CreateAllItemsQuery());
    
            // set these items for retrieval.
            clientContext.Load(listItemCol);
        }
    
        using (exceptionScopeFetch.StartCatch())
        {
            //
        }
    
        using (exceptionScopeFetch.StartFinally())
        {
            //
        }
    }
    
    // get item instances first
    clientContext.ExecuteQuery();
        
    if (exceptionScopeSubmit.HasException)
    {
        ei = new EventInformation()
        {
            LoggingEventType = LoggingEventType.DataSentFailure,
            LoggingSeverity = LoggingSeverity.HighSeverity,
            SerialisedMessage = JsonConvert.SerializeObject(definition),
            ServiceMessage = $"Error at SharePoint exception handler during submit to list: Message: {exceptionScopeFetch.ErrorMessage}",
            StackTrace = exceptionScopeFetch.ServerStackTrace,
            TimeGenerated = DateTime.Now,
            TimeWritten = DateTime.Now,
        };
    
        log.EventLogEntryType = EventLogEntryType.Error;
        log.EventID = LoggingSeverity.HighSeverity;
        log.Message = JsonConvert.SerializeObject(ei);
        AddToLog(log);
    }