Linq to SharePoint. Формирование данных для ProcessBatchData

Небольшой пост о том как формировать данные для пакетной обработки данных в SharePoint (использующей метод SPWeb.ProcessBatchData()), используя модель данных Linq to SharePoint.

ProcessBatchData

Метод ProcessBatchData используется, согласно MSDN, для исполнения множественных запросов в рамках одной транзакции. Метод принимает текстовый параметр, содержащий CAML-запрос.

CAML-запрос для ProcessBatchData

Сам CAML-запрос выглядит примерно так:

  1. <ows:Batch OnError="{Return | Continue}">
  2.   <Method ID="{CommandId}">
  3.     <SetList>[ListId]</SetList>
  4.     <SetVar Name="ID">{[ElementId] | New}</SetVar>
  5.     <SetVar Name="Cmd">{Save | Delete}</SetVar>
  6.     <SetVar Name="urn:schemas-microsoft-com:office:office#Title">
  7.         [TitleFieldValue]
  8.     </SetVar>
  9.   </Method>
  10. </ows:Batch>

В элементе Batch мы задаем реакцию на возникновение ошибок. Return - обработка завершается при регистрации первой ошибки, Continue - обработка будет продолжена. Каждый элемент Batch должен содержать хотя бы один элемент Method. Внутри элемента Method есть три обязательных параметра: первый - SetList, он содержит ID списка, второй - <SetVar Name="ID" />, здесь мы указывает ID элемента списка или текст "New", если элемент ещё не создан. И последний из обязательных - <SetVar Name="Cmd" />, в котором указываем команду, которую необходимо выполнить. Это может быть Delete (удаление) или Save (создание/изменение).

Все имена дополнительных полей необходимо писать с префиксом "urn:schemas-microsoft-com:office:office#", который я вынес в поле BatchFieldPrefix:

  1. /// <summary>
  2. /// Префикс для имен полей
  3. /// </summary>
  4. private const string BatchFieldPrefix = "urn:schemas-microsoft-com:office:office#";

Построение CAML

Формировать CAML-запрос мы будем, используя атрибуты Linq to SharePoint классов. Возвращаясь к моей любимой модели данных, описанной здесь, я создал в базовом классе ZhukDataItem два метода: один для получения команды создания/обновления и второй для получения команды удаления.

Удаление элемента

Для начала, вот метод, возвращающий CAML для удаление элемента:

  1. /// <summary>
  2. /// Генерация команды удаления элемента
  3. /// </summary>
  4. /// <param name="listId">Id списка</param>
  5. /// <returns></returns>
  6. public string GetBatchDeleteCommand(Guid listId)
  7. {
  8.     var sb = new StringBuilder(10);
  9.     // ID метода произволен и должен быть уникален в рамках одного Batch-элемента
  10.     sb.AppendFormat(@"<Method ID=""{0}, Delete"">{1}",
  11.         GUID,
  12.         Environment.NewLine);
  13.     // ID списка
  14.     sb.AppendFormat(@"<SetList>{0}</SetList>{1}",
  15.         listId.ToString(),
  16.         Environment.NewLine);
  17.     sb.AppendFormat(@"<SetVar Name=""ID"">{0}</SetVar>{1}",
  18.         Id.ToString(),
  19.         Environment.NewLine);
  20.     // Указываем, что элемент необходимо удалить
  21.     sb.AppendLine(@"<SetVar Name=""Cmd"">Delete</SetVar>");
  22.     sb.AppendLine(@"</Method>");
  23.     return sb.ToString();
  24. }

Здесь без каких-либо хитростей, все понятно. Вот только ID списка получить из объекта Linq to SharePoint получить нативно не получится, придется реализовывать интерфейс ICustomMapping и "отлавливать" его при вызове метода MapFrom().

Остается только полученный CAML-запрос передать методу SPWeb.ProcessBatchData()

Создание/Изменение элемента

Немного сложней при создании/изменении элемента, т.к. нам придется перебрать все свойства объекта с атрибутом ColumnAttribute и сгенерировать для них элементы SetVar:

  1. /// <summary>
  2. /// Генерация команды создания/изменения элемента
  3. /// </summary>
  4. /// <param name="listId">Id списка</param>
  5. /// <returns></returns>
  6. public string GetBatchSaveCommand(Guid listId)
  7. {
  8.     // Получаем тип. Здесь использовано позднее связывание,
  9.     // т.к. класс базовый и необходимо обеспечить его работу в унаследованных классах
  10.     var objType = GetType();
  11.     // Получаем свойства класса
  12.     var properties = objType.GetProperties();
  13.     var sb = new StringBuilder(10);
  14.     // ID метода произволен и должен быть уникален в рамках одного Batch-элемента
  15.     sb.AppendFormat(@"<Method ID=""{0}, Save"">{1}"
  16.         Id.HasValue ? GUID : Guid.NewGuid(),
  17.         Environment.NewLine);
  18.     // ID списка
  19.     sb.AppendFormat(@"<SetList>{0}</SetList>{1}"
  20.         listId.ToString(),
  21.         Environment.NewLine);
  22.     // ID элемента. Если элемент создается, то вместо ID указываем "New"
  23.     sb.AppendFormat(@"<SetVar Name=""ID"">{0}</SetVar>{1}"
  24.         Id.HasValue ? Id.Value.ToString() : "New",
  25.         Environment.NewLine);
  26.     // Указываем, что элемент необходимо сохранить/создать
  27.     sb.AppendLine(@"<SetVar Name=""Cmd"">Save</SetVar>");
  28.  
  29.     sb.AppendFormat(@"<SetVar Name=""RootFolder"">{0}</SetVar>{1}",
  30.         Path,
  31.         Environment.NewLine);
  32.     // Перебираем свойства класса
  33.     foreach (var property in properties)
  34.     {
  35.         // Получаем атрибуты типа ColumnAttribute
  36.         var attributes = property.GetCustomAttributes(typeof(ColumnAttribute), false);
  37.         foreach (ColumnAttribute att in attributes)
  38.         {
  39.             // Если свойство помечено как ReadOnly, то пропускаем его
  40.             if (att.ReadOnly)
  41.                 continue;
  42.             // Берем поле для хранения значения, 
  43.             // указанное в атрибуте ColumnAttribute
  44.             var field = objType.GetField(att.Storage,
  45.                                         BindingFlags.NonPublic | BindingFlags.Instance);
  46.             // Если такого поля в классе нет, то просматриваем базовые классы
  47.             while (field == null)
  48.             {
  49.                 objType = objType.BaseType;
  50.                 if (objType == nullbreak;
  51.                 field = objType.GetField(att.Storage,
  52.                                         BindingFlags.NonPublic | BindingFlags.Instance);
  53.             }
  54.             if (field != null)
  55.             {
  56.                 sb.AppendFormat(@"<SetVar Name=""{0}{1}"">{2}</SetVar>{3}"
  57.                     BatchFieldPrefix, 
  58.                     att.Name, 
  59.                     field.GetValue(this),
  60.                     Environment.NewLine);
  61.             }
  62.         }
  63.     }
  64.     sb.AppendLine(@"</Method>");
  65.     return sb.ToString();
  66. }

Теперь, используя Linq to SharePoint можно изменять/удалять элементы списка пакетно, примерно так:

  1. List<Employee> employees = GetEmployeesMethod();
  2. using (var site = new SPSite(siteUrl))
  3. {
  4.     using (var web = site.OpenWeb())
  5.     {
  6.         web.AllowUnsafeUpdates = true;
  7.         var list = web.Lists["Employees"];
  8.         var methods = employees.Aggregate(string.Empty,
  9.                         (current, item) => current + item.GetBatchDeleteCommand(list.ID));
  10.         var batch = string.Format(@"<?xml version=""1.0"" encoding=""UTF-8""?>
  11.                     <ows:Batch OnError=""Continue"">{0}</ows:Batch>", methods);
  12.         web.ProcessBatchData(batch);
  13.     }
  14. }

Позже я напишу про производительность этого подхода.

Виталий Жуков

Виталий Жуков

SharePoint архитектор, разработчик, тренер, Microsoft MVP (Office Development). Более 15 лет опыта работы с SharePoint, Dynamics CRM, Office 365, и другими продуктами и сервисами Microsoft.

Смотрите также

Развертывание списков и библиотек с помощью SPFx-решений

Развертывание списков и библиотек с помощью SPFx-решений

SharePoint. Drag-and-Drop Загрузчик файлов

SharePoint. Drag-and-Drop Загрузчик файлов

CSOM. Загрузка файлов

CSOM. Загрузка файлов

SharePoint List REST API. Часть 2

SharePoint List REST API. Часть 2

SharePoint Framework. Создание веб-части на Angular

SharePoint Framework. Создание веб-части на Angular

SharePoint List REST API. Часть 1

SharePoint List REST API. Часть 1

Презентация с доклада о SharePoint Framework

Презентация с доклада о SharePoint Framework

SharePoint Framework. Создаем AngularJS 1.x Client WebPart

SharePoint Framework. Создаем AngularJS 1.x Client WebPart

SharePoint. Регистрация CSS и JavaScript с помощью DelegateControl

SharePoint. Регистрация CSS и JavaScript с помощью DelegateControl

SharePoint. Расширяем REST API

SharePoint. Расширяем REST API

SharePoint Excel Services. Создаем кредитный калькулятор

SharePoint Excel Services. Создаем кредитный калькулятор

SharePoint Ribbon API. Использование ToggleButton

SharePoint Ribbon API. Использование ToggleButton

SharePoint 2013. How To: настройка входящей почты для разработчиков

SharePoint 2013. How To: настройка входящей почты для разработчиков

Мифы и правда о Linq to SharePoint

Мифы и правда о Linq to SharePoint

5 особенностей SPSiteDataQuery

5 особенностей SPSiteDataQuery

SharePoint 2013. Введение в SharePoint App. Часть 2

SharePoint 2013. Введение в SharePoint App. Часть 2

SharePoint 2013. Введение в SharePoint App. Часть 1

SharePoint 2013. Введение в SharePoint App. Часть 1

Превью для веб-части в SharePoint 2010/2013

Превью для веб-части в SharePoint 2010/2013

SharePoint 2013. Еще немного о новых контролах

SharePoint 2013. Еще немного о новых контролах

SharePoint 2013. Контрол ClientPeoplePicker

SharePoint 2013. Контрол ClientPeoplePicker

SharePoint 2013. Контрол ImageCrop

SharePoint 2013. Контрол ImageCrop

SharePoint 2013. Тип поля Geolocation

SharePoint 2013. Тип поля Geolocation

Создание типа поля в SharePoint

Создание типа поля в SharePoint

SharePoint 2010. Длительные операции с обновляемым статусом

SharePoint 2010. Длительные операции с обновляемым статусом

Linq to SharePoint. Создаем ContentIterator

Linq to SharePoint. Создаем ContentIterator

Linq to SharePoint. Получение данных из другой коллекции сайтов

Linq to SharePoint. Получение данных из другой коллекции сайтов

Linq to SharePoint. Версионность

Linq to SharePoint. Версионность

SharePoint. Получение URL-адреса иконки для документа

SharePoint. Получение URL-адреса иконки для документа

SharePoint 2010. PostBack для Fluent Ribbon API

SharePoint 2010. PostBack для Fluent Ribbon API

Linq to SharePoint. Блокировка документов

Linq to SharePoint. Блокировка документов

Linq to SharePoint. Паттерн Repository

Linq to SharePoint. Паттерн Repository

Linq to SharePoint. Получение мета-данных списка

Linq to SharePoint. Получение мета-данных списка

Linq to SharePoint. Мапинг полей

Linq to SharePoint. Мапинг полей

Linq to SharePoint. Сравнение производительности с Camlex.NET

Linq to SharePoint. Сравнение производительности с Camlex.NET

Linq to SharePoint. Часть 5. Поля Choice и MultiChoice

Linq to SharePoint. Часть 5. Поля Choice и MultiChoice

Linq to SharePoint. Часть 4. Dynamic LINQ

Linq to SharePoint. Часть 4. Dynamic LINQ

Linq to SharePoint. Особенности. Часть 3

Linq to SharePoint. Особенности. Часть 3

Linq to SharePoint. Особенности. Часть 2

Linq to SharePoint. Особенности. Часть 2

SharePoint 2010. PeopleEditor. Установка значения

SharePoint 2010. PeopleEditor. Установка значения

SharePoint 2010. Настройка входящей почты для кастомного списка

SharePoint 2010. Настройка входящей почты для кастомного списка

Linq to Sharepoint. Особенности

Linq to Sharepoint. Особенности

EntityFramework. Оптимистические блокировки

EntityFramework. Оптимистические блокировки