Linq to SharePoint. Repository pattern

Linq to SharePoint is a native Microsoft data provider translating LINQ-expressions into CAML-queries for retrieving data from SharePoint lists (document libraries). In this post I'll show how to implement repository pattern for Linq to SharePoint.

Repository

Working with data in the SharePoint environment has its own spicifics and according to it first define the requirements for the repository:

  • Support for anonymous access - required, for example, when creating a public website based on SharePoint 2010;
  • ReadOnly mode for better performance;
  • Access to SharePoint object model without any additinal initilization of SPSite, SPWeb, SPList or other objects

Based on these requirements, we will implement the repository pattern. Data model I've defined in the post about Linq to SharePoint features [in Russian].

Entity, DataContext

The base class for all other classes as described in the data model is ZhukDataItem class, associated with the element Content Type (Id = 0x01). Custom data context is not needed. It is enough for native Microsoft.SharePoint.Linq.DataContext.

Thus, the above in the form of a class diagram:

Repository pattern implementation for SharePoint 2010

When initializing the repository creates a context for working with data, determines whether the user is anonymous and initiate the retrieving of information about the list (EntityList<TEntity>):

  1. /// <summary>
  2. /// Base repository class
  3. /// </summary>
  4. /// <typeparam name="TEntity">Entity type</typeparam>
  5. /// <typeparam name="TContext">DataContext type</typeparam>
  6. public abstract class BaseRepository<TEntity, TContext>
  7.     where TEntity : ZhukDataItem, new()
  8.     where TContext : DataContext
  9. {
  10.     protected readonly string WebUrl;
  11.     protected readonly string ListName;
  12.     protected readonly bool ReadOnly;
  13.     public readonly bool IsAnonymous;
  14.  
  15.     /// <summary>
  16.     /// Repository initialization
  17.     /// </summary>
  18.     /// <param name="listName">List name</param>
  19.     /// <param name="webUrl">Site Url</param>
  20.     /// <param name="readOnly">ReadOnly mode</param>
  21.     protected BaseRepository(string listName, string webUrl, bool readOnly)
  22.     {
  23.         ReadOnly = readOnly;
  24.         ListName = listName;
  25.         WebUrl = webUrl;
  26.  
  27.         var ctx = SPContext.Current;
  28.         IsAnonymous = ctx != null && SPContext.Current.Web.CurrentUser == null;
  29.  
  30.         InitializeParameters();
  31.     }
  32.  
  33.     /// <summary>
  34.     /// Repository initialization for current site
  35.     /// </summary>
  36.     /// <param name="listName">List name</param>
  37.     /// <param name="readOnly">ReadOnly mode</param>
  38.     protected BaseRepository(string listName, bool readOnly)
  39.         : this(listName,
  40.             SPContext.Current.Web.Url, readOnly)
  41.     { }
  42.  
  43.     /// <summary>
  44.     /// <param name="readOnly">ReadOnly mode</param>
  45.     /// </summary>
  46.     /// <param name="listName">List name</param>
  47.     protected BaseRepository(string listName)
  48.         : this(listName, true)
  49.     { }
  50.  
  51.     /// <summary>
  52.     /// Repository initialization in ReadOnly mode
  53.     /// </summary>
  54.     /// <param name="listName">List name</param>
  55.     /// <param name="webUrl">Site url</param>
  56.     protected BaseRepository(string listName, string webUrl)
  57.         : this(listName, webUrl, true)
  58.     { }
  59.  
  60.     /// <summary>
  61.     /// Repository initialization
  62.     /// </summary>
  63.     private void InitializeParameters()
  64.     {
  65.         if (IsAnonymous)
  66.         {
  67.             RunAsAdmin(() =>
  68.             {
  69.                 CurrentContext =
  70.                     (TContext)Activator.CreateInstance(typeof(TContext),
  71.                     new object[] { WebUrl });
  72.                 CurrentContext.ObjectTrackingEnabled = !ReadOnly;
  73.                 CurrentList = CurrentContext.GetList<TEntity>(ListName);
  74.             });
  75.         }
  76.         else
  77.         {
  78.             CurrentContext =
  79.                 (TContext)Activator.CreateInstance(typeof(TContext),
  80.                 new object[] { WebUrl });
  81.             CurrentContext.ObjectTrackingEnabled = !ReadOnly;
  82.             CurrentList = CurrentContext.GetList<TEntity>(ListName);
  83.         }
  84.     }
  85.  
  86.     /// <summary>
  87.     /// DataContext
  88.     /// </summary>
  89.     private TContext CurrentContext { getset; }
  90.  
  91.     /// <summary>
  92.     /// List/Document Library
  93.     /// </summary>
  94.     private EntityList<TEntity> CurrentList { getset; }
  95.     
  96.     //... other methods
  97. }

If the user is anonymous, the data context will be created using Application Pool identity. Bear that in mind while implementing this trick.

CRUD operations

Now the turn for these operations: Create, Read, Update, Delete (CRUD).

Create/Update entity

Creating and Updating entity is based on attaching antity to the data context.

  1. /// <summary>
  2. /// Save entity
  3. /// </summary>
  4. /// <param name="entity">Entity</param>
  5. public TEntity SaveEntity(TEntity entity)
  6. {
  7.     if (!entity.Id.HasValue)
  8.         entity.EntityState = EntityState.ToBeInserted;
  9.     CurrentList.Attach(entity);
  10.     CurrentContext.SubmitChanges();
  11.     return entity;
  12. }

In the case of creating a new record (in other words object has no identifier) should specify the state of equal EntityState.ToBeInserted.

Delete entity

Deleting entity from list/document librarie using Linq to SharePoint simple enough to do this, simply pass an object to be deleted to DataContext.DeleteOnSubmit method.

  1. /// <summary>
  2. /// Delete entity
  3. /// </summary>
  4. /// <param name="id">Entity Id</param>
  5. public void DeleteEntity(int id)
  6. {
  7.     var query = CurrentList
  8.         .ScopeToFolder(string.Empty, true)
  9.         .Where(entry => entry.Id == id);
  10.     var entity = query.FirstOrDefault();
  11.     if (entity != null)
  12.     {
  13.         CurrentList.DeleteOnSubmit(entity);
  14.     }
  15.     CurrentContext.SubmitChanges();
  16. }

The object is enough to get by using the "minimum" content type and then delete it.

Retrieve entity

This is the simplest in the Linq to SharePoint. In the case of reading a record, select it by Id, 'cause it is unique in a list:

  1. /// <summary>
  2. /// Retrieve entity
  3. /// </summary>
  4. /// <param name="id">Id</param>
  5. public TEntity GetEntity(int id)
  6. {
  7.     var query = CurrentList
  8.         .ScopeToFolder(string.Empty, true)
  9.         .Where(entry => entry.Id == id);
  10.     return query.FirstOrDefault();
  11. }

To retrieve a collection of entities count of methods will be somewhat larger:

  1. /// <summary>
  2. /// Retrieve a collection of entities from all folders
  3. /// </summary>
  4. public IQueryable<TEntity> GetEntityCollection()
  5. {
  6.     return GetEntityCollection(entry => true);
  7. }
  8.  
  9. /// <summary>
  10. /// Retrieve a collection of entities from all folders
  11. /// </summary>
  12. /// <param name="expression">Predicate</param>
  13. public IQueryable<TEntity> GetEntityCollection(
  14.     Expression<Func<TEntity, bool>> expression)
  15. {
  16.     return GetEntityCollection(expression, string.Empty, true, 0);
  17. }
  18.  
  19. /// <summary>
  20. /// Retrieve a collection of entities from folder and subfolders
  21. /// </summary>
  22. /// <param name="expression">Predicate</param>
  23. /// <param name="path">Path</param>
  24. public IQueryable<TEntity> GetEntityCollection(
  25.     Expression<Func<TEntity, bool>> expression, string path)
  26. {
  27.     return GetEntityCollection(expression, path, true, 0);
  28. }
  29.  
  30. /// <summary>
  31. /// Retrieve a collection of entities from folder
  32. /// </summary>
  33. /// <param name="expression">Predicate</param>
  34. /// <param name="path">Folder</param>
  35. /// <param name="recursive">Retrieve data from subfolder</param>
  36. public IQueryable<TEntity> GetEntityCollection(
  37.     Expression<Func<TEntity, bool>> expression,
  38.     string path, bool recursive)
  39. {
  40.     return GetEntityCollection(expression, path, recursive, 0);
  41. }
  42.  
  43. /// <summary>
  44. /// Retrieve a collection of entities from folder
  45. /// </summary>
  46. /// <param name="expression">Predicate</param>
  47. /// <param name="path">Folder</param>
  48. /// <param name="recursive">Retrieve data from subfolder</param>
  49. /// <param name="maxRows">Maximum row count</param>
  50. public IQueryable<TEntity> GetEntityCollection(
  51.     Expression<Func<TEntity, bool>> expression,
  52.     string path, bool recursive, int maxRows)
  53. {
  54.     var query = CurrentList
  55.         .ScopeToFolder(path, recursive)
  56.         .Where(expression);
  57.     if (maxRows > 0)
  58.         query = query.Take(maxRows);
  59.     return query;
  60. }

It is important that the methods as a predicate taking a parameter of type Expression, as it stores the entire tree queries, therefore Linq to SharePoint can analysis it. If the transfer request as a Func, Linq to SharePoint implicitly retrieve all items from the list.

Access to the SharePoint Object Model

How to access the object model I wrote in the post about getting the list meta-data [in russian]. The class represents the meta-data list containing the property List, returns the SPList:

  1. /// <summary>
  2. /// List' meta-data
  3. /// </summary>
  4. public EntityListMetaData MetaData
  5. {
  6.     get
  7.     {
  8.         return EntityListMetaData.GetMetaData(CurrentList);
  9.     }
  10. }

A special case of the repository

Now, using the repository base class, you can easily create their own repository for working with SharePoint list (document library). For example, here is the repository for working with items in the Employees list:

  1. public sealed class EmployeeRepository 
  2.     : BaseRepository<Employee, ZhukDataContext>
  3. {
  4.     /// <summary>
  5.     /// List name
  6.     /// </summary>
  7.     private const string EmployeeListName = "Employees";
  8.  
  9.     public EmployeeRepository() 
  10.         : base(EmployeeListName) { }
  11.     public EmployeeRepository(bool readOnly) 
  12.         : base(EmployeeListName, readOnly) { }
  13.     public EmployeeRepository(string webUrl) 
  14.         : base(EmployeeListName, webUrl) { }
  15.     public EmployeeRepository(string webUrl, bool readOnly) 
  16.         : base(EmployeeListName, webUrl, readOnly) { }
  17.  
  18.     // Additional method
  19.     /// <summary>
  20.     /// Получение сотрудников
  21.     /// </summary>
  22.     /// <param name="managerId">Id сотрудника</param>
  23.     public IEnumerable<Employee> GetEmployees(int managerId)
  24.     {
  25.         return GetEntityCollection(emp => emp.ManagerId == managerId);
  26.     }
  27. }

A minimum of extra code and maximum functionality. For the last here are few lines of code demonstrating the using of a repository for working with list items:

  1. var repository = new EmployeeRepository("http://sharepointserver");
  2.  
  3. // Get employee
  4. var employee = repository.GetEntity(1);
  5.             
  6. // Create and save employee 
  7. var employeeNew = new Employee {Title = "Vitaly Zhukov"};
  8. repository.SaveEntity(employeeNew);
  9.  
  10. // Delete employee
  11. repository.DeleteEntity(2);
  12.  
  13. // Call additional method
  14. var empList = repository.GetEmployees(1);
  15.  
  16. // Get meta-data of the list
  17. var md = repository.MetaData;
  18.  
  19. // Get fields of the list
  20. var fields = md.Fields.Where(f => f.Hidden == false);

Share

Comments