Как рекурсивно изменять значения ListItems в Sharepoint 2013 с помощью CSOM

Моя ситуация

Привет, у моего клиента есть SharePoint Server 2013 on Premise. Он хочет записывать метаданные в несколько файлов. В частности, он хочет выбрать папку, а затем приложение должно записать одно и то же значение в столбец для каждого файла / папки в этой папке.

Что я уже сделал

Я написал CAML-запрос, который возвращает все файлы / папки ниже выбранной папки. Затем я записываю значение для каждого элемента и обновляю его.

Что я хочу сделать

Я хочу написать приложение, в котором мой клиент может выбирать папку. После этого программа должна обновить все файлы в этой папке. Я хочу написать это приложение на C # и CSOM.

Моя проблема

Это очень медленно. При наличии нескольких тысяч файлов в папке создание одной папки занимает несколько часов.

Мои вопросы

Есть ли способ ускорить процесс?
Может ли CAML-Query также обновлять значения?
Каков рекомендуемый способ делать то, что я хочу делать?
Есть ли другой способ достичь того, чего хочет мой клиент?

Спасибо за вашу помощь.


person Simon Balling    schedule 18.01.2016    source источник


Ответы (1)


Вы можете воспользоваться функцией под названием пакетной обработки запросов, которая, в свою очередь, может значительно влияют на производительность за счет минимизации количества сообщений, передаваемых между клиентом и сервером.

Из документации:

Модель программирования CSOM построена на пакетной обработке запросов. Когда вы работаете с CSOM, вы можете выполнять серию операций с данными на объекте ClientContext. Эти операции отправляются на сервер в одном запросе, когда вы вызываете метод ClientContext.BeginExecuteQuery.

Для сравнения продемонстрируем два способа обновления нескольких элементов списка:

Стандартный способ:

foreach (var item in items)
{
   item["LastReviewed"] = DateTime.Now;
   item.Update();
   ctx.ExecuteQuery();
}

Пакетное обновление:

foreach (var item in items)
{
   item["LastReviewed"] = DateTime.Now;
   item.Update();
}
ctx.ExecuteQuery();

Примечание: во втором подходе на сервер отправляется только один пакетный запрос.

Полный пример

var listTitle = "Documents";
var folderUrl = "Archive/2013";

using (var ctx = GetContext(url, userName, password))
{
     //1. Get list items operation
     var list = ctx.Web.Lists.GetByTitle(listTitle);
     var items = list.GetListItems(folderUrl);
     ctx.Load(items);
     ctx.ExecuteQuery();


     //2. Update list items operation
     var watch = Stopwatch.StartNew();
     foreach (var item in items)
     {
           if(Convert.ToInt32(item["FSObjType"]) == 1) //skip folders
               continue;

           item["LastReviewed"] = DateTime.Now;
           item.Update();
           //ctx.ExecuteQuery();
           Console.WriteLine("{0} has been updated", item["FileRef"]);
     }
     ctx.ExecuteQuery();  //execute batched request
     watch.Stop();
     Console.WriteLine("Update operation completed: {0}", watch.ElapsedMilliseconds); 

     Console.ReadLine();
}

где GetListItems - это метод расширения для получения элементов списка, находящихся в определенной папке:

using System.Linq;
using Microsoft.SharePoint.Client;

namespace SharePoint.Client.Extensions
{
    public static class ListCollectionExtensions
    {

        /// <summary>
        /// Get list items located under specific folder 
        /// </summary>
        /// <param name="list"></param>
        /// <param name="relativeFolderUrl"></param>
        /// <returns></returns>
        public static ListItemCollection GetListItems(this List list, string relativeFolderUrl)
        {
            var ctx = list.Context;
            if (!list.IsPropertyAvailable("RootFolder"))
            {
                ctx.Load(list.RootFolder, f => f.ServerRelativeUrl);
                ctx.ExecuteQuery();
            }
            var folderUrl = list.RootFolder.ServerRelativeUrl + "/" + relativeFolderUrl;
            var qry = CamlQuery.CreateAllItemsQuery();
            qry.FolderServerRelativeUrl = folderUrl;
            var items = list.GetItems(qry);
            return items;
        }
    }
}
person Vadim Gremyachev    schedule 19.01.2016
comment
Спасибо за помощь. Я столкнулся с двумя проблемами (возможно, они одна) с вашим кодом. Сначала я получил ошибку о том, что запрос слишком велик и не должен превышать 2 МБ. Затем я создаю счетчик, который выполняет запрос после X элементов. Что определенно быстрее, чем моя первая встреча. есть ли способ получить максимальное количество обновлений программно, чтобы добиться максимальной производительности? другая ошибка была аналогичной, но в ней говорилось, что я использую слишком много ресурсов. - person Simon Balling; 19.01.2016
comment
Саймон, это действительно ожидаемая ошибка, когда размер запроса становится слишком большим (по умолчанию 2 МБ) и ваш подход (разделение пакетных запросов) правильный. Я посмотрю, как правильно определить текущий размер запроса, и свяжусь с вами. - person Vadim Gremyachev; 19.01.2016
comment
Хорошо, спасибо. Что именно делает строка if (! List.IsPropertyAvailable (RootFolder))? Еще хочу включить в обновление корневую папку. Как я могу этого добиться? - person Simon Balling; 19.01.2016
comment
Это выражение на человеческом языке означает: давайте проверим, загружено ли свойство RootFolder объекта List (свойство FolderServerRelativeUrl имеет следующий формат /[web]/[list or library]/[folder] и list.RootFolder.ServerRelativeUrl предназначено для получения URL-адреса списка / библиотеки), если нет, то запросите его с сервера - person Vadim Gremyachev; 19.01.2016
comment
Спасибо. Еще один вопрос: моя библиотека называется Archive, а моя папка - Group1. Таким образом, мой folderUrl будет Archive / Group1 /. С текущим кодом я обновлю все элементы ниже Group1. Я хочу также обновить папку Group1. Как я могу этого добиться? - person Simon Balling; 20.01.2016