Tuesday, December 30, 2008

Repository pattern

[Edited on June 6, 2009.]

This is my implementation of the Repository pattern in C#; I am using the ServiceLocator defined in my previous post.

This is the main interface:

  1. public interface IRepository<TEntity, TId>  
  2.   where TEntity: IEntity<TId>  
  3. {  
  4.   /// <summary>  
  5.   /// Returns all entities in the repository  
  6.   /// </summary>  
  7.   /// <returns>List of entities</returns>  
  8.   IEnumerable<TEntity> GetAll();  
  9.   
  10.   /// <summary>  
  11.   /// Returns all entities matching a criteria  
  12.   /// </summary>  
  13.   /// <param name="criteria">Criteria to be matched</param>  
  14.   /// <returns>All entities matching the criteria; should not be null, but it can be an empty list</returns>  
  15.   IEnumerable<TEntity> FindAll(Expression<Func<TEntity, bool>> criteria);  
  16.   
  17.   /// <summary>  
  18.   /// Returns the first entity matching a criteria  
  19.   /// </summary>  
  20.   /// <param name="criteria">Criteria to be matched</param>  
  21.   /// <returns>The first entity that matches a criteria; can be null</returns>  
  22.   TEntity Find(Expression<Func<TEntity, bool>> criteria);  
  23.   
  24.   /// <summary>  
  25.   /// Finds a record with a given id  
  26.   /// </summary>  
  27.   /// <param name="id">Key to search</param>  
  28.   /// <returns>The record with the given id or null if none exists</returns>  
  29.   TEntity Find(TId id);  
  30. }  


Now, I am always creating a primary key for each table, and nowadays I call it Id. It's normally an autoincrementing integer, but for users or other sensitive information I will probably switch to GUIDs, so the type of the primary key should be a parameter. Here is the entity interface, representing a record in a table:

  1. namespace Helper  
  2. {  
  3.   public interface IEntity<TId>  
  4.   {  
  5.     TId Id { getset; }  
  6.   }  
  7. }  


As you can see, the only relevant thing is the primary key :)

I've extracted the updates to another repository... this is incompletely refined, you might need more granularity (some repositories might support updates but not deletes, for example).

  1. public interface IUpdateableRepository<TEntity, TId>  
  2.   where TEntity : IEntity<TId>  
  3. {  
  4.   /// <summary>  
  5.   /// Adds an entity to the repository; the repository may change the entity's Id on adding  
  6.   /// </summary>  
  7.   /// <param name="entity">Entity to add</param>  
  8.   void Add(TEntity entity);  
  9.   
  10.   /// <summary>  
  11.   /// Deletes an entity  
  12.   /// </summary>  
  13.   /// <param name="entity">Entity to delete</param>  
  14.   void Delete(TEntity entity);  
  15.   
  16.   /// <summary>  
  17.   /// Updates an entity  
  18.   /// </summary>  
  19.   /// <param name="entity">Entity to update</param>  
  20.   void Update(TEntity entity);  
  21.   
  22.   /// <summary>  
  23.   /// Commits all changes made so far  
  24.   /// </summary>  
  25.   void Commit();  
  26. }  


Finally, this is a possible implementation of a repository, using the DI pattern (and the ServiceLocator class):

  1. public class Repository<TEntity, TId> : IRepository<TEntity, TId>  
  2.   where TEntity : class, IEntity<TId>, new()  
  3. {  
  4.   protected readonly IEnumerable<TEntity> data;  
  5.   
  6.   public Repository(IEnumerable<TEntity> data)  
  7.   {  
  8.     this.data = data;  
  9.   }  
  10.  
  11.   #region IRepository<TEntity>  
  12.   
  13.   public virtual IEnumerable<TEntity> GetAll()  
  14.   {  
  15.     return data;  
  16.   }  
  17.   
  18.   public virtual IEnumerable<TEntity> FindAll(Expression<Func<TEntity, bool>> criteria)  
  19.   {  
  20.     var cond = criteria.Compile();  
  21.     return from entity in data where cond(entity) select entity;  
  22.   }  
  23.   
  24.   public virtual TEntity Find(Expression<Func<TEntity, bool>> criteria)  
  25.   {  
  26.     var cond = criteria.Compile();  
  27.     return (from entity in data where cond(entity) select entity).FirstOrDefault();  
  28.   }  
  29.   
  30.   public virtual TEntity Find(TId id)  
  31.   {  
  32.     return Find(entity => entity.Id.Equals(id));  
  33.   }  
  34.  
  35.   #endregion  
  36. }  


  1. public class LinqRepository<TEntity, TId> : Repository<TEntity, TId>, IUpdateableRepository<TEntity, TId>  
  2.   where TEntity : class, IEntity<TId>, new()  
  3. {  
  4.   protected readonly DataContext db;  
  5.   
  6.   public LinqRepository()  
  7.     : this(ServiceLocator.GetInstanceOf<DataContext>())  
  8.   {  
  9.   }  
  10.   
  11.   public LinqRepository(DataContext db)  
  12.     : base(db.GetTable<TEntity>())  
  13.   {  
  14.     this.db = db;  
  15.   }  
  16.  
  17.   #region IRepository<TEntity>  
  18.  
  19.   #endregion  
  20.  
  21.   #region IUpdateableRepository<TEntity>  
  22.   
  23.   public void Add(TEntity entity)  
  24.   {  
  25.     ((Table<TEntity>) data).InsertOnSubmit(entity);  
  26.   }  
  27.   
  28.   public void Delete(TEntity entity)  
  29.   {  
  30.     ((Table<TEntity>) data).DeleteOnSubmit(entity);  
  31.   }  
  32.   
  33.   public void Update(TEntity entity)  
  34.   {  
  35.     ((Table<TEntity>) data).Attach(entity, true);  
  36.   }  
  37.   
  38.   public void Commit()  
  39.   {  
  40.     db.SubmitChanges();  
  41.   }  
  42.  
  43.   #endregion  
  44. }  



Note: since I initially wrote this, I have read a few more articles discussing the need of using the Repository pattern at all. Here's a list of pro and con articles... decide for yourself :)

Ayende's article, first one I read on the subject.
Greg Young's first, second and third article.
Richard Dingwall's article.
This article suggests an interesting split - you should have two domains, one for changing your domain and one for querying it, and the two would have very different repositories. I have to think about it :)

No comments: