Linq to SharePoint. Паттерн Repository
Linq to SharePoint - это провайдер от Microsoft, который позволяет транслировать LINQ-выражения в CAML-запросы для работы с данными списков и библиотек документов SharePoint. Сегодня я покажу, как можно реализовать паттерн репозитория для работы с данными SharePoint 2010.
Репозиторий
Так как работа с данными SharePoint имеет свою специфику, для начала определим требования к будущему репозиторию:
- Поддержка анонимного доступа - потребуется, например, при создании интернет-сайта на базе SharePoint 2010;
 - Режим "только для чтения" - при работе с Linq to SharePoint отключение отслеживания изменений объектов это позволит обеспечить лучшую производительность. Подробнее об этом здесь;
 - Доступ к объектной модели SharePoint - наш репозиторий должен обеспечить некую инкапсуляцию для простого доступа к объектной модели SharePoint 2010.
 
Исходя из этих требований будем реализовывать паттерн репозитория. А модель данных для этого мы будем использовать описанную мною в февральском посте, посвященном Linq to SharePoint.
Entity, DataContext
Базовым классом для всех других классов, описанных в модели данных будет класс ZhukDataItem, привязанный к базовому типу содержимого SharePoint - элемент (Id = 0x01).
Контекст для работы с данными здесь свой не понадобится. Вполне хватит стандартного Microsoft.SharePoint.Linq.DataContext. При желании можно создать и свой, реализовав в нем дополнительные методы, например получение списка по его URL'у и прочее.
Итак, вышеописанное в виде диаграммы классов:

При инициализации репозитория мы будем создавать контекст для работы с данными, определять является ли пользователь анонимным и инициализировать загрузку информации о списке (EntityList<TEntity>):
- /// <summary>
 - /// Базовый класс репозитория
 - /// </summary>
 - /// <typeparam name="TEntity">Тип сущности</typeparam>
 - /// <typeparam name="TContext">Тип контекста данных</typeparam>
 - public abstract class BaseRepository<TEntity, TContext>
 -     where TEntity : ZhukDataItem, new()
 -     where TContext : DataContext
 - {
 -     protected readonly string WebUrl;
 -     protected readonly string ListName;
 -     protected readonly bool ReadOnly;
 -     public readonly bool IsAnonymous;
 -  
 -     /// <summary>
 -     /// Инициализация репозитория
 -     /// </summary>
 -     /// <param name="listName">Название списка</param>
 -     /// <param name="webUrl">Url сайта</param>
 -     /// <param name="readOnly">Режим "Только для чтения"</param>
 -     protected BaseRepository(string listName, string webUrl, bool readOnly)
 -     {
 -         ReadOnly = readOnly;
 -         ListName = listName;
 -         WebUrl = webUrl;
 -  
 -         var ctx = SPContext.Current;
 -         IsAnonymous = ctx != null && SPContext.Current.Web.CurrentUser == null;
 -  
 -         InitializeParameters();
 -     }
 -  
 -     /// <summary>
 -     /// Инициализация репозитория для текущего сайта
 -     /// </summary>
 -     /// <param name="listName">Название списка</param>
 -     /// <param name="readOnly">Режим "Только для чтения"</param>
 -     protected BaseRepository(string listName, bool readOnly)
 -         : this(listName,
 -             SPContext.Current.Web.Url, readOnly)
 -     { }
 -  
 -     /// <summary>
 -     /// Инициализация репозитория для текущего сайта в режиме "Только для чтения"
 -     /// </summary>
 -     /// <param name="listName">Название списка</param>
 -     protected BaseRepository(string listName)
 -         : this(listName, true)
 -     { }
 -  
 -     /// <summary>
 -     /// Инициализация репозитория в режиме "Только для чтения"
 -     /// </summary>
 -     /// <param name="listName">Название списка</param>
 -     /// <param name="webUrl">Url сайта</param>
 -     protected BaseRepository(string listName, string webUrl)
 -         : this(listName, webUrl, true)
 -     { }
 -  
 -     /// <summary>
 -     /// Инициализация параметров репозитория
 -     /// </summary>
 -     private void InitializeParameters()
 -     {
 -         if (IsAnonymous)
 -         {
 -             RunAsAdmin(() =>
 -             {
 -                 CurrentContext =
 -                     (TContext)Activator.CreateInstance(typeof(TContext),
 -                     new object[] { WebUrl });
 -                 CurrentContext.ObjectTrackingEnabled = !ReadOnly;
 -                 CurrentList = CurrentContext.GetList<TEntity>(ListName);
 -             });
 -         }
 -         else
 -         {
 -             CurrentContext =
 -                 (TContext)Activator.CreateInstance(typeof(TContext),
 -                 new object[] { WebUrl });
 -             CurrentContext.ObjectTrackingEnabled = !ReadOnly;
 -             CurrentList = CurrentContext.GetList<TEntity>(ListName);
 -         }
 -     }
 -  
 -     /// <summary>
 -     /// Контекст данных
 -     /// </summary>
 -     private TContext CurrentContext { get; set; }
 -  
 -     /// <summary>
 -     /// Список/библиотека элементов
 -     /// </summary>
 -     private EntityList<TEntity> CurrentList { get; set; }
 -     
 -     //... Прочие методы
 - }
 
Если пользователь анонимен, то контекст данных будет создан с правами учетной записи, от имени которой работает пул приложений в IIS. Здесь надо не забывать это и учитывать при реализации.
CRUD операции
Теперь очередь для операций с данными Create, Read, Update, Delete (CRUD).
Создание/Сохранение элемента
Создание и сохранение элементов в репозитории будет основано на присоединении сущности к контексту.
- /// <summary>
 - /// Сохранение элемента списка/библиотеки
 - /// </summary>
 - /// <param name="entity">Элемент списка/библиотеки</param>
 - public TEntity SaveEntity(TEntity entity)
 - {
 -     if (!entity.Id.HasValue)
 -         entity.EntityState = EntityState.ToBeInserted;
 -     CurrentList.Attach(entity);
 -     CurrentContext.SubmitChanges();
 -     return entity;
 - }
 
Здесь, в случае создания новой записи (если у объекта отсутствует идентификатор (поле Id)) надо указать его состояние равным EntityState.ToBeInserted.
Удаление элемента
Удаление элементов списков/библиотек документов в Linq to SharePoint достаточно просто, для этого необходимо просто передать методу DataContext.DeleteOnSubmit объект или коллекцию объектов, подлежащих удалению.
- /// <summary>
 - /// Удаление элемента списка/библиотеки
 - /// </summary>
 - /// <param name="id">Id элемента</param>
 - public 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();
 - }
 
В случае удаления элемента из списка, достаточно получить его, использую "минимальный" тип содержимого и затем удалить.
Чтение данных
Это самое простое в Linq to SharePoint. В случае чтения одной записи, выбирать её достаточно по полю Id, т.к. оно уникально в пределах одного списка:
- /// <summary>
 - /// Получение элемента списка/библиотеки
 - /// </summary>
 - /// <param name="id">Id элемента</param>
 - public TEntity GetEntity(int id)
 - {
 -     var query = CurrentList
 -         .ScopeToFolder(string.Empty, true)
 -         .Where(entry => entry.Id == id);
 -     return query.FirstOrDefault();
 - }
 
В случае чтения коллекции объектов методов будет несколько больше:
- /// <summary>
 - /// Получение коллекции объектов из всех папок списка
 - /// </summary>
 - public IQueryable<TEntity> GetEntityCollection()
 - {
 -     return GetEntityCollection(entry => true);
 - }
 -  
 - /// <summary>
 - /// Получение коллекции объектов из всех папок списка
 - /// </summary>
 - /// <param name="expression">Предикат</param>
 - public IQueryable<TEntity> GetEntityCollection(
 -     Expression<Func<TEntity, bool>> expression)
 - {
 -     return GetEntityCollection(expression, string.Empty, true, 0);
 - }
 -  
 - /// <summary>
 - /// Получение коллекции объектов из указанной папки её и дочерних папок
 - /// </summary>
 - /// <param name="expression">Предикат</param>
 - /// <param name="path">Папка</param>
 - public IQueryable<TEntity> GetEntityCollection(
 -     Expression<Func<TEntity, bool>> expression, string path)
 - {
 -     return GetEntityCollection(expression, path, true, 0);
 - }
 -  
 - /// <summary>
 - /// Получение коллекции объектов из указанной папки
 - /// </summary>
 - /// <param name="expression">Предикат</param>
 - /// <param name="path">Папка</param>
 - /// <param name="recursive">Выбор из дорчерних папок</param>
 - public IQueryable<TEntity> GetEntityCollection(
 -     Expression<Func<TEntity, bool>> expression,
 -     string path, bool recursive)
 - {
 -     return GetEntityCollection(expression, path, recursive, 0);
 - }
 -  
 - /// <summary>
 - /// Получение коллекции объектов из указанной папки
 - /// </summary>
 - /// <param name="expression">Предикат</param>
 - /// <param name="path">Папка</param>
 - /// <param name="recursive">Выбор из дорчерних папок</param>
 - /// <param name="maxRows">Максимальное кол-во строк</param>
 - public IQueryable<TEntity> GetEntityCollection(
 -     Expression<Func<TEntity, bool>> expression,
 -     string path, bool recursive, int maxRows)
 - {
 -     var query = CurrentList
 -         .ScopeToFolder(path, recursive)
 -         .Where(expression);
 -     if (maxRows > 0)
 -         query = query.Take(maxRows);
 -     return query;
 - }
 
Здесь важно то, что методы в качестве предиката принимают параметр типа Expression, т.к. он хранит все дерево запросов и потому поддается анализу Linq to SharePoint. Если же передать запрос в виде Func, Linq to SharePoint неявно запросит все данные их списка. Подробнее можно почитать в посте о реализации аналоги T-SQL'ного оператора IN.
Доступ к объектной модели SharePoint
Механизм доступа к объектной модели я писал в посте о получении мета-данных списка. Класс представляющий мета-данные списка содержат свойство List, возвращающее тот самый SPList:
- /// <summary>
 - /// Мета-данные списка/библиотеки
 - /// </summary>
 - public EntityListMetaData MetaData
 - {
 -     get
 -     {
 -         return EntityListMetaData.GetMetaData(CurrentList);
 -     }
 - }
 
Частный случай репозитория
Теперь, используя базовый класс репозитория можно с легкостью создавать свои репозитории для работы со списками/библиотеками документов SharePoint. Вот, для примера, репозиторий для работы с элементами списка Employees:
- public sealed class EmployeeRepository 
 -     : BaseRepository<Employee, ZhukDataContext>
 - {
 -     /// <summary>
 -     /// Название списка
 -     /// </summary>
 -     private const string EmployeeListName = "Employees";
 -  
 -     public EmployeeRepository() 
 -         : base(EmployeeListName) { }
 -     public EmployeeRepository(bool readOnly) 
 -         : base(EmployeeListName, readOnly) { }
 -     public EmployeeRepository(string webUrl) 
 -         : base(EmployeeListName, webUrl) { }
 -     public EmployeeRepository(string webUrl, bool readOnly) 
 -         : base(EmployeeListName, webUrl, readOnly) { }
 -  
 -     // Дополнительный метод
 -     /// <summary>
 -     /// Получение сотрудников
 -     /// </summary>
 -     /// <param name="managerId">Id сотрудника</param>
 -     public IEnumerable<Employee> GetEmployees(int managerId)
 -     {
 -         return GetEntityCollection(emp => emp.ManagerId == managerId);
 -     }
 - }
 
Минимум дополнительного кода и максимум функциональности. Напоследок несколько строк кода, демонстрирующих использования репозитория для работы с элементами списка:
- var repository = new EmployeeRepository("http://sharepointserver");
 -  
 - // Получение сотрудника
 - var employee = repository.GetEntity(1);
 -             
 - // Создание и сохранение сотрудника 
 - var employeeNew = new Employee {Title = "Иванов Иван Иванович"};
 - repository.SaveEntity(employeeNew);
 -  
 - // Удаление сотрудника
 - repository.DeleteEntity(2);
 -  
 - // Вызов дополнительного метода
 - var empList = repository.GetEmployees(1);
 -  
 - // Получение мета-данных списка без инициализации SPWeb и прочего
 - var md = repository.MetaData;
 -  
 - // Получение списка полей
 - var fields = md.Fields.Where(f => f.Hidden == false);
 
 








































