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. }

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


Поделиться

Коментарии