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

Исходные коды - 13063

Получение версий элемента в SharePoint

Для получения версий элемента списка надо использовать свойство Versions объекта SPListItem, который возвращает нам коллекцию объектов SPListItemVersion. Между классами SPListItem и SPListItemVersion нет никакой иерархической зависимости.

Поэтому нам понадобится ещё один конструктор для базового класса, который будет принимать в качестве параметра объект типа SPListItemVersion. Его функционал по сути повторяет функционал конструктора, принимающего объект SPListItem, который я описывал в посте Linq to SharePoint. Часть 4. Dynamic LINQ:

  1. /// <summary>
  2. /// Create an instance of class from SPitem
  3. /// </summary>
  4. /// <param name="item">SPListItemVersion instance</param>
  5. public ZhukDataItem(SPListItemVersion item)
  6. {
  7.     if (item == nullreturn;
  8.     var objType = GetType();
  9.     var properties = objType.GetProperties();
  10.     foreach (var property in properties)
  11.     {
  12.         var attributes = property.GetCustomAttributes(typeof(ColumnAttribute), false);
  13.         foreach (ColumnAttribute att in attributes)
  14.         {
  15.             var field = objType.GetField(att.Storage,
  16.                 BindingFlags.NonPublic | BindingFlags.Instance);
  17.             while (field == null)
  18.             {
  19.                 objType = objType.BaseType;
  20.                 if (objType == nullbreak;
  21.                 field = objType.GetField(att.Storage,
  22.                     BindingFlags.NonPublic | BindingFlags.Instance);
  23.             }
  24.             if (field != null)
  25.             {
  26.                 switch (att.FieldType)
  27.                 {
  28.                     case "Lookup":
  29.                         try
  30.                         {
  31.                             var fv = new SPFieldLookupValue(
  32.                                 (item[att.Name] ?? string.Empty).ToString());
  33.                             if (att.IsLookupId)
  34.                             {
  35.                                 field.SetValue(this, fv.LookupId);
  36.                             }
  37.                             else
  38.                             {
  39.                                 field.SetValue(this, fv.LookupValue);
  40.                             }
  41.                         }
  42.                         catch (ArgumentException)
  43.                         {
  44.                             field.SetValue(this, item[att.Name]);
  45.                         }
  46.                         break;
  47.                     case "User":
  48.                         try
  49.                         {
  50.                             var fv = new SPFieldUserValue(item.Fields.List.ParentWeb,                                                     (item[att.Name] ?? string.Empty).ToString());
  51.                             if (att.IsLookupId)
  52.                             {
  53.                                 field.SetValue(this, fv.LookupId);
  54.                             }
  55.                             else
  56.                             {
  57.                                 field.SetValue(this, fv.LookupValue);
  58.                             }
  59.                         }
  60.                         catch (ArgumentException)
  61.                         {
  62.                             field.SetValue(this, item[att.Name]);
  63.                         }
  64.                         break;
  65.                     case "Guid":
  66.                         field.SetValue(thisnew Guid(item[att.Name].ToString()));
  67.                         break;
  68.                     default:
  69.                         field.SetValue(this, item[att.Name]);
  70.                         break;
  71.                 }
  72.             }
  73.         }
  74.     }
  75. }

Конструктор создан, теперь надо получить объект типа SPListItem. Для этого будем использовать механизм получения мета-данных из непубличных свойств контекста данных Linq to SharePoint.

Для получения версий элемента в репозитории я создал вот такие приватный и публичный методы:

  1. /// <summary>
  2. /// Retrieve versions of entity
  3. /// </summary>
  4. /// <param name="entity">Entity</param>
  5. /// <returns>Collection of entities</returns>
  6. public IEnumerable<TEntity> GetVersions(TEntity entity)
  7. {
  8.     if (entity.Id.HasValue)
  9.         return GetVersions(entity.Id.Value);
  10.     throw new ArgumentException("Entity has no id property value");
  11. }
  12.  
  13. private IEnumerable<TEntity> GetVersions(int id)
  14. {
  15.     var item = MetaData.List.GetItemById(id);
  16.     var versions = item.Versions
  17.         .Cast<SPListItemVersion>()
  18.         .Select(v => Activator.CreateInstance(typeof(TEntity), v))
  19.         .Cast<TEntity>()
  20.         .ToList();
  21.     return versions;
  22. }

В репозитории свойство MetaData.List возвращает объект типа SPList. Все эти данный Linq to SharePoint получает из объектной модели SharePoint при инициализации объекта EntityList.

Так как в случае получения объекта из списка/библиотеки документов SharePoint, используя Linq to SharePoint и описанный мною репозитории мы получаем объекты одного типа с одинаковыми идентификаторами, нам понадобиться их как-то отличать. SharePoint использует для этого булево свойство _IsCurrentVersion. Не мудрствуя лукаво, будем делать тоже самое. Для этого добавим в базовый класс свойство IsCurrentVersion:

  1. /// <summary>
  2. /// Is this entity is current version flag
  3. /// </summary>
  4. [Column(Name = "_IsCurrentVersion", Storage = "_isCurrentVersion"
  5.  ReadOnly = true, FieldType = "Boolean")]
  6. public bool? IsCurrentVersion
  7. {
  8.     get
  9.     {
  10.         return _isCurrentVersion;
  11.     }
  12.     set
  13.     {
  14.         if ((value == _isCurrentVersion)) return;
  15.         OnPropertyChanging("IsCurrentVersion", _isCurrentVersion);
  16.         _isCurrentVersion = value;
  17.         OnPropertyChanged("IsCurrentVersion");
  18.     }
  19. }

Цепная реакция заставляет пересмотреть логику метода удаления сущностей. У меня этот метод теперь представлен в репозитории вот так:

  1. /// <summary>
  2. /// Удаление элемента списка/библиотеки
  3. /// </summary>
  4. /// <param name="id">Id элемента</param>
  5. public void DeleteEntity(TEntity entity)
  6. {
  7.     if (!entity.Id.HasValue)
  8.         throw new ArgumentException("Entity has no identifier");
  9.     if (entity.IsCurrentVersion == true)
  10.     {
  11.         DeleteEntity(entity.Id.Value);
  12.     }
  13.     else
  14.     {
  15.         if (!entity.Version.HasValue)
  16.             throw new ArgumentException("Entity version has no identifier");
  17.         DeleteEntityVersion(entity.Id.Value, entity.Version.Value);
  18.     }
  19. }
  20.  
  21. private void DeleteEntity(int id)
  22. {
  23.     var query = CurrentList
  24.         .ScopeToFolder(string.Empty, true)
  25.         .Where(entry => entry.Id == id);
  26.     var entity = query.FirstOrDefault();
  27.     if (entity != null)
  28.     {
  29.         CurrentList.DeleteOnSubmit(entity);
  30.     }
  31.     CurrentContext.SubmitChanges();
  32. }
  33.  
  34. private void DeleteEntityVersion(int entityId, int versionId)
  35. {
  36.     var item = MetaData.List.GetItemById(entityId);
  37.     var version = item.Versions.GetVersionFromID(versionId);
  38.     version.Delete();
  39. }

Теперь в методе DeleteEntity есть проверка, позволяющая удалять версии элементов.

Применение

Вот пример использования версионности в Linq to SharePoint на примере списка сотрудников, в котором мы изменяем сущность и удаляем предыдущую версию:

  1. // Инициализируем репозиторий
  2. var repository = new EmployeeRepository(SiteUrl, false);
  3. // Выбираем данные
  4. var query = repository.GetEntityCollection(emp => 
  5.     emp.IsCurrentVersion == true && emp.Id == 5);
  6. // Получаем сотрудника
  7. var employee = query.FirstOrDefault();
  8. // Изменяем свойство
  9. employee.Title = GetRandomEmployeeTitle();
  10. // Сохраняем изменения. Теперь у employee.Version увеличена на единицу
  11. repository.SaveEntity(employee);
  12. // Берем предыдущую версию элемента
  13. var previosVersion = repository
  14.     .GetVersions(employee)
  15.     .OrderByDescending(x => x.Version)
  16.     .FirstOrDefault();
  17. // Удаляем предыдущую версию
  18. // Все остальные версии, в том чиле текущая, остаются неизменными
  19. repository.DeleteEntity(previosVersion);

CodePlex

Так как я во многих постах использую один и тот же демонстрационный проект, реализация которого меняется от поста к посту, то я решил выложить его на codeplex и приводить ссылку на соответствующий посту коммит.

Исходные коды - 13063

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

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

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. Получение данных из другой коллекции сайтов

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. Формирование данных для ProcessBatchData

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

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. Оптимистические блокировки