Linq to SharePoint. Версионность
Исходные коды - 13063
Получение версий элемента в SharePoint
Для получения версий элемента списка надо использовать свойство Versions объекта SPListItem, который возвращает нам коллекцию объектов SPListItemVersion. Между классами SPListItem и SPListItemVersion нет никакой иерархической зависимости.
Поэтому нам понадобится ещё один конструктор для базового класса, который будет принимать в качестве параметра объект типа SPListItemVersion. Его функционал по сути повторяет функционал конструктора, принимающего объект SPListItem, который я описывал в посте Linq to SharePoint. Часть 4. Dynamic LINQ:
- /// <summary>
- /// Create an instance of class from SPitem
- /// </summary>
- /// <param name="item">SPListItemVersion instance</param>
- public ZhukDataItem(SPListItemVersion item)
- {
- if (item == null) return;
- var objType = GetType();
- var properties = objType.GetProperties();
- foreach (var property in properties)
- {
- var attributes = property.GetCustomAttributes(typeof(ColumnAttribute), false);
- foreach (ColumnAttribute att in attributes)
- {
- 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)
- {
- switch (att.FieldType)
- {
- case "Lookup":
- try
- {
- var fv = new SPFieldLookupValue(
- (item[att.Name] ?? string.Empty).ToString());
- if (att.IsLookupId)
- {
- field.SetValue(this, fv.LookupId);
- }
- else
- {
- field.SetValue(this, fv.LookupValue);
- }
- }
- catch (ArgumentException)
- {
- field.SetValue(this, item[att.Name]);
- }
- break;
- case "User":
- try
- {
- var fv = new SPFieldUserValue(item.Fields.List.ParentWeb, (item[att.Name] ?? string.Empty).ToString());
- if (att.IsLookupId)
- {
- field.SetValue(this, fv.LookupId);
- }
- else
- {
- field.SetValue(this, fv.LookupValue);
- }
- }
- catch (ArgumentException)
- {
- field.SetValue(this, item[att.Name]);
- }
- break;
- case "Guid":
- field.SetValue(this, new Guid(item[att.Name].ToString()));
- break;
- default:
- field.SetValue(this, item[att.Name]);
- break;
- }
- }
- }
- }
- }
Конструктор создан, теперь надо получить объект типа SPListItem. Для этого будем использовать механизм получения мета-данных из непубличных свойств контекста данных Linq to SharePoint.
Для получения версий элемента в репозитории я создал вот такие приватный и публичный методы:
- /// <summary>
- /// Retrieve versions of entity
- /// </summary>
- /// <param name="entity">Entity</param>
- /// <returns>Collection of entities</returns>
- public IEnumerable<TEntity> GetVersions(TEntity entity)
- {
- if (entity.Id.HasValue)
- return GetVersions(entity.Id.Value);
- throw new ArgumentException("Entity has no id property value");
- }
-
- private IEnumerable<TEntity> GetVersions(int id)
- {
- var item = MetaData.List.GetItemById(id);
- var versions = item.Versions
- .Cast<SPListItemVersion>()
- .Select(v => Activator.CreateInstance(typeof(TEntity), v))
- .Cast<TEntity>()
- .ToList();
- return versions;
- }
В репозитории свойство MetaData.List возвращает объект типа SPList. Все эти данный Linq to SharePoint получает из объектной модели SharePoint при инициализации объекта EntityList.
Так как в случае получения объекта из списка/библиотеки документов SharePoint, используя Linq to SharePoint и описанный мною репозитории мы получаем объекты одного типа с одинаковыми идентификаторами, нам понадобиться их как-то отличать. SharePoint использует для этого булево свойство _IsCurrentVersion. Не мудрствуя лукаво, будем делать тоже самое. Для этого добавим в базовый класс свойство IsCurrentVersion:
- /// <summary>
- /// Is this entity is current version flag
- /// </summary>
- [Column(Name = "_IsCurrentVersion", Storage = "_isCurrentVersion",
- ReadOnly = true, FieldType = "Boolean")]
- public bool? IsCurrentVersion
- {
- get
- {
- return _isCurrentVersion;
- }
- set
- {
- if ((value == _isCurrentVersion)) return;
- OnPropertyChanging("IsCurrentVersion", _isCurrentVersion);
- _isCurrentVersion = value;
- OnPropertyChanged("IsCurrentVersion");
- }
- }
Цепная реакция заставляет пересмотреть логику метода удаления сущностей. У меня этот метод теперь представлен в репозитории вот так:
- /// <summary>
- /// Удаление элемента списка/библиотеки
- /// </summary>
- /// <param name="id">Id элемента</param>
- public void DeleteEntity(TEntity entity)
- {
- if (!entity.Id.HasValue)
- throw new ArgumentException("Entity has no identifier");
- if (entity.IsCurrentVersion == true)
- {
- DeleteEntity(entity.Id.Value);
- }
- else
- {
- if (!entity.Version.HasValue)
- throw new ArgumentException("Entity version has no identifier");
- DeleteEntityVersion(entity.Id.Value, entity.Version.Value);
- }
- }
-
- private void DeleteEntity(int id)
- {
- var query = CurrentList
- .ScopeToFolder(string.Empty, true)
- .Where(entry => entry.Id == id);
- var entity = query.FirstOrDefault();
- if (entity != null)
- {
- CurrentList.DeleteOnSubmit(entity);
- }
- CurrentContext.SubmitChanges();
- }
-
- private void DeleteEntityVersion(int entityId, int versionId)
- {
- var item = MetaData.List.GetItemById(entityId);
- var version = item.Versions.GetVersionFromID(versionId);
- version.Delete();
- }
Теперь в методе DeleteEntity есть проверка, позволяющая удалять версии элементов.
Применение
Вот пример использования версионности в Linq to SharePoint на примере списка сотрудников, в котором мы изменяем сущность и удаляем предыдущую версию:
- // Инициализируем репозиторий
- var repository = new EmployeeRepository(SiteUrl, false);
- // Выбираем данные
- var query = repository.GetEntityCollection(emp =>
- emp.IsCurrentVersion == true && emp.Id == 5);
- // Получаем сотрудника
- var employee = query.FirstOrDefault();
- // Изменяем свойство
- employee.Title = GetRandomEmployeeTitle();
- // Сохраняем изменения. Теперь у employee.Version увеличена на единицу
- repository.SaveEntity(employee);
- // Берем предыдущую версию элемента
- var previosVersion = repository
- .GetVersions(employee)
- .OrderByDescending(x => x.Version)
- .FirstOrDefault();
- // Удаляем предыдущую версию
- // Все остальные версии, в том чиле текущая, остаются неизменными
- repository.DeleteEntity(previosVersion);
CodePlex
Так как я во многих постах использую один и тот же демонстрационный проект, реализация которого меняется от поста к посту, то я решил выложить его на codeplex и приводить ссылку на соответствующий посту коммит.
Исходные коды - 13063