Linq to SharePoint. Формирование данных для ProcessBatchData
Небольшой пост о том как формировать данные для пакетной обработки данных в SharePoint (использующей метод SPWeb.ProcessBatchData()
), используя модель данных Linq to SharePoint.
ProcessBatchData
Метод ProcessBatchData
используется, согласно MSDN, для исполнения множественных запросов в рамках одной транзакции. Метод принимает текстовый параметр, содержащий CAML-запрос.
CAML-запрос для ProcessBatchData
Сам CAML-запрос выглядит примерно так:
- <ows:Batch OnError="{Return | Continue}">
- <Method ID="{CommandId}">
- <SetList>[ListId]</SetList>
- <SetVar Name="ID">{[ElementId] | New}</SetVar>
- <SetVar Name="Cmd">{Save | Delete}</SetVar>
- <SetVar Name="urn:schemas-microsoft-com:office:office#Title">
- [TitleFieldValue]
- </SetVar>
- </Method>
- </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:
- /// <summary>
- /// Префикс для имен полей
- /// </summary>
- private const string BatchFieldPrefix = "urn:schemas-microsoft-com:office:office#";
Построение CAML
Формировать CAML-запрос мы будем, используя атрибуты Linq to SharePoint классов. Возвращаясь к моей любимой модели данных, описанной здесь, я создал в базовом классе ZhukDataItem
два метода: один для получения команды создания/обновления и второй для получения команды удаления.
Удаление элемента
Для начала, вот метод, возвращающий CAML для удаление элемента:
- /// <summary>
- /// Генерация команды удаления элемента
- /// </summary>
- /// <param name="listId">Id списка</param>
- /// <returns></returns>
- public string GetBatchDeleteCommand(Guid listId)
- {
- var sb = new StringBuilder(10);
- // ID метода произволен и должен быть уникален в рамках одного Batch-элемента
- sb.AppendFormat(@"<Method ID=""{0}, Delete"">{1}",
- GUID,
- Environment.NewLine);
- // ID списка
- sb.AppendFormat(@"<SetList>{0}</SetList>{1}",
- listId.ToString(),
- Environment.NewLine);
- sb.AppendFormat(@"<SetVar Name=""ID"">{0}</SetVar>{1}",
- Id.ToString(),
- Environment.NewLine);
- // Указываем, что элемент необходимо удалить
- sb.AppendLine(@"<SetVar Name=""Cmd"">Delete</SetVar>");
- sb.AppendLine(@"</Method>");
- return sb.ToString();
- }
Здесь без каких-либо хитростей, все понятно. Вот только ID списка получить из объекта Linq to SharePoint получить нативно не получится, придется реализовывать интерфейс ICustomMapping и "отлавливать" его при вызове метода MapFrom().
Остается только полученный CAML-запрос передать методу SPWeb.ProcessBatchData()
Создание/Изменение элемента
Немного сложней при создании/изменении элемента, т.к. нам придется перебрать все свойства объекта с атрибутом ColumnAttribute и сгенерировать для них элементы SetVar:
- /// <summary>
- /// Генерация команды создания/изменения элемента
- /// </summary>
- /// <param name="listId">Id списка</param>
- /// <returns></returns>
- public string GetBatchSaveCommand(Guid listId)
- {
- // Получаем тип. Здесь использовано позднее связывание,
- // т.к. класс базовый и необходимо обеспечить его работу в унаследованных классах
- var objType = GetType();
- // Получаем свойства класса
- var properties = objType.GetProperties();
- var sb = new StringBuilder(10);
- // ID метода произволен и должен быть уникален в рамках одного Batch-элемента
- sb.AppendFormat(@"<Method ID=""{0}, Save"">{1}",
- Id.HasValue ? GUID : Guid.NewGuid(),
- Environment.NewLine);
- // ID списка
- sb.AppendFormat(@"<SetList>{0}</SetList>{1}",
- listId.ToString(),
- Environment.NewLine);
- // ID элемента. Если элемент создается, то вместо ID указываем "New"
- sb.AppendFormat(@"<SetVar Name=""ID"">{0}</SetVar>{1}",
- Id.HasValue ? Id.Value.ToString() : "New",
- Environment.NewLine);
- // Указываем, что элемент необходимо сохранить/создать
- sb.AppendLine(@"<SetVar Name=""Cmd"">Save</SetVar>");
-
- sb.AppendFormat(@"<SetVar Name=""RootFolder"">{0}</SetVar>{1}",
- Path,
- Environment.NewLine);
- // Перебираем свойства класса
- foreach (var property in properties)
- {
- // Получаем атрибуты типа ColumnAttribute
- var attributes = property.GetCustomAttributes(typeof(ColumnAttribute), false);
- foreach (ColumnAttribute att in attributes)
- {
- // Если свойство помечено как ReadOnly, то пропускаем его
- if (att.ReadOnly)
- continue;
- // Берем поле для хранения значения,
- // указанное в атрибуте ColumnAttribute
- var field = objType.GetField(att.Storage,
- BindingFlags.NonPublic | BindingFlags.Instance);
- // Если такого поля в классе нет, то просматриваем базовые классы
- while (field == null)
- {
- objType = objType.BaseType;
- if (objType == null) break;
- field = objType.GetField(att.Storage,
- BindingFlags.NonPublic | BindingFlags.Instance);
- }
- if (field != null)
- {
- sb.AppendFormat(@"<SetVar Name=""{0}{1}"">{2}</SetVar>{3}",
- BatchFieldPrefix,
- att.Name,
- field.GetValue(this),
- Environment.NewLine);
- }
- }
- }
- sb.AppendLine(@"</Method>");
- return sb.ToString();
- }
Теперь, используя Linq to SharePoint можно изменять/удалять элементы списка пакетно, примерно так:
- List<Employee> employees = GetEmployeesMethod();
- using (var site = new SPSite(siteUrl))
- {
- using (var web = site.OpenWeb())
- {
- web.AllowUnsafeUpdates = true;
- var list = web.Lists["Employees"];
- var methods = employees.Aggregate(string.Empty,
- (current, item) => current + item.GetBatchDeleteCommand(list.ID));
- var batch = string.Format(@"<?xml version=""1.0"" encoding=""UTF-8""?>
- <ows:Batch OnError=""Continue"">{0}</ows:Batch>", methods);
- web.ProcessBatchData(batch);
- }
- }
Позже я напишу про производительность этого подхода.