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


Поделиться

Коментарии