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:
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>):
- /// <summary>
- /// Base repository class
- /// </summary>
- /// <typeparam name="TEntity">Entity type</typeparam>
- /// <typeparam name="TContext">DataContext type</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>
- /// Repository initialization
- /// </summary>
- /// <param name="listName">List name</param>
- /// <param name="webUrl">Site Url</param>
- /// <param name="readOnly">ReadOnly mode</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>
- /// Repository initialization for current site
- /// </summary>
- /// <param name="listName">List name</param>
- /// <param name="readOnly">ReadOnly mode</param>
- protected BaseRepository(string listName, bool readOnly)
- : this(listName,
- SPContext.Current.Web.Url, readOnly)
- { }
-
- /// <summary>
- /// <param name="readOnly">ReadOnly mode</param>
- /// </summary>
- /// <param name="listName">List name</param>
- protected BaseRepository(string listName)
- : this(listName, true)
- { }
-
- /// <summary>
- /// Repository initialization in ReadOnly mode
- /// </summary>
- /// <param name="listName">List name</param>
- /// <param name="webUrl">Site url</param>
- protected BaseRepository(string listName, string webUrl)
- : this(listName, webUrl, true)
- { }
-
- /// <summary>
- /// Repository initialization
- /// </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>
- /// DataContext
- /// </summary>
- private TContext CurrentContext { get; set; }
-
- /// <summary>
- /// List/Document Library
- /// </summary>
- private EntityList<TEntity> CurrentList { get; set; }
-
- //... other methods
- }
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.
- /// <summary>
- /// Save entity
- /// </summary>
- /// <param name="entity">Entity</param>
- public TEntity SaveEntity(TEntity entity)
- {
- if (!entity.Id.HasValue)
- entity.EntityState = EntityState.ToBeInserted;
- CurrentList.Attach(entity);
- CurrentContext.SubmitChanges();
- return entity;
- }
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.
- /// <summary>
- /// Delete entity
- /// </summary>
- /// <param name="id">Entity 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();
- }
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:
- /// <summary>
- /// Retrieve entity
- /// </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();
- }
To retrieve a collection of entities count of methods will be somewhat larger:
- /// <summary>
- /// Retrieve a collection of entities from all folders
- /// </summary>
- public IQueryable<TEntity> GetEntityCollection()
- {
- return GetEntityCollection(entry => true);
- }
-
- /// <summary>
- /// Retrieve a collection of entities from all folders
- /// </summary>
- /// <param name="expression">Predicate</param>
- public IQueryable<TEntity> GetEntityCollection(
- Expression<Func<TEntity, bool>> expression)
- {
- return GetEntityCollection(expression, string.Empty, true, 0);
- }
-
- /// <summary>
- /// Retrieve a collection of entities from folder and subfolders
- /// </summary>
- /// <param name="expression">Predicate</param>
- /// <param name="path">Path</param>
- public IQueryable<TEntity> GetEntityCollection(
- Expression<Func<TEntity, bool>> expression, string path)
- {
- return GetEntityCollection(expression, path, true, 0);
- }
-
- /// <summary>
- /// Retrieve a collection of entities from folder
- /// </summary>
- /// <param name="expression">Predicate</param>
- /// <param name="path">Folder</param>
- /// <param name="recursive">Retrieve data from subfolder</param>
- public IQueryable<TEntity> GetEntityCollection(
- Expression<Func<TEntity, bool>> expression,
- string path, bool recursive)
- {
- return GetEntityCollection(expression, path, recursive, 0);
- }
-
- /// <summary>
- /// Retrieve a collection of entities from folder
- /// </summary>
- /// <param name="expression">Predicate</param>
- /// <param name="path">Folder</param>
- /// <param name="recursive">Retrieve data from subfolder</param>
- /// <param name="maxRows">Maximum row count</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;
- }
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:
- /// <summary>
- /// List' meta-data
- /// </summary>
- public EntityListMetaData MetaData
- {
- get
- {
- return EntityListMetaData.GetMetaData(CurrentList);
- }
- }
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:
- public sealed class EmployeeRepository
- : BaseRepository<Employee, ZhukDataContext>
- {
- /// <summary>
- /// List name
- /// </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) { }
-
- // Additional method
- /// <summary>
- /// Получение сотрудников
- /// </summary>
- /// <param name="managerId">Id сотрудника</param>
- public IEnumerable<Employee> GetEmployees(int managerId)
- {
- return GetEntityCollection(emp => emp.ManagerId == managerId);
- }
- }
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:
- var repository = new EmployeeRepository("http://sharepointserver");
-
- // Get employee
- var employee = repository.GetEntity(1);
-
- // Create and save employee
- var employeeNew = new Employee {Title = "Vitaly Zhukov"};
- repository.SaveEntity(employeeNew);
-
- // Delete employee
- repository.DeleteEntity(2);
-
- // Call additional method
- var empList = repository.GetEmployees(1);
-
- // Get meta-data of the list
- var md = repository.MetaData;
-
- // Get fields of the list
- var fields = md.Fields.Where(f => f.Hidden == false);