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:
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:
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).
Finally, this is a possible implementation of a repository, using the DI pattern (and the ServiceLocator class):
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 :)
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:
- public interface IRepository<TEntity, TId>
- where TEntity: IEntity<TId>
- {
- /// <summary>
- /// Returns all entities in the repository
- /// </summary>
- /// <returns>List of entities</returns>
- IEnumerable<TEntity> GetAll();
- /// <summary>
- /// Returns all entities matching a criteria
- /// </summary>
- /// <param name="criteria">Criteria to be matched</param>
- /// <returns>All entities matching the criteria; should not be null, but it can be an empty list</returns>
- IEnumerable<TEntity> FindAll(Expression<Func<TEntity, bool>> criteria);
- /// <summary>
- /// Returns the first entity matching a criteria
- /// </summary>
- /// <param name="criteria">Criteria to be matched</param>
- /// <returns>The first entity that matches a criteria; can be null</returns>
- TEntity Find(Expression<Func<TEntity, bool>> criteria);
- /// <summary>
- /// Finds a record with a given id
- /// </summary>
- /// <param name="id">Key to search</param>
- /// <returns>The record with the given id or null if none exists</returns>
- TEntity Find(TId id);
- }
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:
- namespace Helper
- {
- public interface IEntity<TId>
- {
- TId Id { get; set; }
- }
- }
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).
- public interface IUpdateableRepository<TEntity, TId>
- where TEntity : IEntity<TId>
- {
- /// <summary>
- /// Adds an entity to the repository; the repository may change the entity's Id on adding
- /// </summary>
- /// <param name="entity">Entity to add</param>
- void Add(TEntity entity);
- /// <summary>
- /// Deletes an entity
- /// </summary>
- /// <param name="entity">Entity to delete</param>
- void Delete(TEntity entity);
- /// <summary>
- /// Updates an entity
- /// </summary>
- /// <param name="entity">Entity to update</param>
- void Update(TEntity entity);
- /// <summary>
- /// Commits all changes made so far
- /// </summary>
- void Commit();
- }
Finally, this is a possible implementation of a repository, using the DI pattern (and the ServiceLocator class):
- public class Repository<TEntity, TId> : IRepository<TEntity, TId>
- where TEntity : class, IEntity<TId>, new()
- {
- protected readonly IEnumerable<TEntity> data;
- public Repository(IEnumerable<TEntity> data)
- {
- this.data = data;
- }
- #region IRepository<TEntity>
- public virtual IEnumerable<TEntity> GetAll()
- {
- return data;
- }
- public virtual IEnumerable<TEntity> FindAll(Expression<Func<TEntity, bool>> criteria)
- {
- var cond = criteria.Compile();
- return from entity in data where cond(entity) select entity;
- }
- public virtual TEntity Find(Expression<Func<TEntity, bool>> criteria)
- {
- var cond = criteria.Compile();
- return (from entity in data where cond(entity) select entity).FirstOrDefault();
- }
- public virtual TEntity Find(TId id)
- {
- return Find(entity => entity.Id.Equals(id));
- }
- #endregion
- }
- public class LinqRepository<TEntity, TId> : Repository<TEntity, TId>, IUpdateableRepository<TEntity, TId>
- where TEntity : class, IEntity<TId>, new()
- {
- protected readonly DataContext db;
- public LinqRepository()
- : this(ServiceLocator.GetInstanceOf<DataContext>())
- {
- }
- public LinqRepository(DataContext db)
- : base(db.GetTable<TEntity>())
- {
- this.db = db;
- }
- #region IRepository<TEntity>
- #endregion
- #region IUpdateableRepository<TEntity>
- public void Add(TEntity entity)
- {
- ((Table<TEntity>) data).InsertOnSubmit(entity);
- }
- public void Delete(TEntity entity)
- {
- ((Table<TEntity>) data).DeleteOnSubmit(entity);
- }
- public void Update(TEntity entity)
- {
- ((Table<TEntity>) data).Attach(entity, true);
- }
- public void Commit()
- {
- db.SubmitChanges();
- }
- #endregion
- }
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 :)
Comments