Introduction
The purpose of this article is to describe the technique I have used to implement the Repository pattern in .NET applications. I will provide a brief description of the Repository pattern and LINQ-to-SQL; however, if you are unfamiliar with these technologies, you should research them elsewhere. The goals of my implementation are:
- it must be a general purpose design that can be reused for many projects
- it must facilitate domain driven design
- it must facilitate unit testing and testing in general
- it must allow the domain model to avoid dependencies on infrastructure
- it must provide strongly typed querying
Repository Pattern
The Repository Pattern, according to Martin Fowler, provides a "layer of abstraction over the mapping layer where query construction code is concentrated", to "minimize duplicate query logic". In practice, it is usually a collection of data access services, grouped in a similar way to the domain model classes.
By accessing repositories via interfaces, the Repository pattern helps to break the dependency between the domain model and the data access code. This is invaluable for unit testing because the domain model can be isolated.
I implement the Repository pattern by defining one repository class for each domain model entity that requires specialized data access methods (other than the standard create, read, update, and delete). If an entity does not require specialized data access methods, then I will use a generic repository for that entity. A repository class contains the specialized data access methods required for its corresponding domain model entity.
The following class diagram shows an example implementation with two domain entity classes: Shape
and Vertex
. Shape
has a specialized repository (IShapeRepository
). Vertex
does not have a specialized repository, so it will just use the generic repository (IRepository<Vertex>
).
LINQ-to-SQL
LINQ is a strongly typed way of querying data. LINQ-to-SQL is a dialect of LINQ that allows the querying of a SQL Server database. It also includes object / relational mapping and tools for generating domain model classes from a database schema. LINQ is an excellent addition to object / relational mapping tools because it facilitates strongly typed queries, such as:
IList<Shape> threeSidedShapes =
_genericShapeRepository.FindAll(shape =>
shape.NumberOfSides == 3).Take(5).ToList();
IRepository<T>
The generic interface IRepository<T>
defines the methods that are required on each repository.
public interface IRepository<T> where T : class
{
IEnumerable<T> All();
IEnumerable<T> FindAll(Func<T, bool> exp);
T Single(Func<T, bool> exp);
T First(Func<T, bool> exp);
void MarkForDeletion(T entity);
T CreateInstance();
void SaveAll();
}
Repository<T>
IRepository<T>
is implemented by a generic repository base class, Repository<T>
. Repository<T>
is a base implementation that provides data access functionality for all entities. If an entity (T
) does not require a specialized repository, then its data access will be done through Repository<T>
.
public class Repository<T> : IRepository<T>
where T : class
{
protected IDataContextFactory _dataContextFactory;
public IEnumerable<T> All()
{
return GetTable;
}
public IEnumerable<T> FindAll(Func<T, bool> exp)
{
return GetTable.Where<T>(exp);
}
public T Single(Func<T, bool> exp)
{
return GetTable.Single(exp);
}
public T First(Func<T, bool> exp)
{
return GetTable.First(exp);
}
public virtual void MarkForDeletion(T entity)
{
_dataContextFactory.Context.GetTable<T>().DeleteOnSubmit(entity);
}
public virtual T CreateInstance()
{
T entity = Activator.CreateInstance<T>();
GetTable.InsertOnSubmit(entity);
return entity;
}
public void SaveAll()
{
_dataContextFactory.SaveAll();
}
public Repository(IDataContextFactory dataContextFactory)
{
_dataContextFactory = dataContextFactory;
}
#region Properties
private string PrimaryKeyName
{
get { return TableMetadata.RowType.IdentityMembers[0].Name; }
}
private System.Data.Linq.Table<T> GetTable
{
get { return _dataContextFactory.Context.GetTable<T>(); }
}
private System.Data.Linq.Mapping.MetaTable TableMetadata
{
get { return _dataContextFactory.Context.Mapping.GetTable(typeof(T)); }
}
private System.Data.Linq.Mapping.MetaType ClassMetadata
{
get { return _dataContextFactory.Context.Mapping.GetMetaType(typeof(T)); }
}
#endregion
}
IShapeRepository and ShapeRepository
It is usually desirable to provide more specialised repositories for entity classes. If our domain included a shape entity, we might like to have a ShapeRepository
with a RetrieveByNumberOfSides(int sideCount)
method. Such a class would be exposed to consumers as a specialized interface IShapeRepository
:
public interface IShapeRepository : IRepository<Shape>
{
Shape RetrieveByNumberOfSides(int sideCount)
}
public class ShapeRepository : Repository<Shape>, IShapeRepository
{
public Shape RetrieveByNumberOfSides(int sideCount)
{
return FindAll(shape => shape.NumberOfSides == sideCount);
}
}
Usage
We now have a fully functioning, decoupled repository implementation. A class might use the repositories as follows:
public class ApplicationService
{
private IRepository<Shape> _genericShapeRepository;
private IShapeRepository _specializedShapeRepository;
public ApplicationService(IRepository<Shape> genericShapeRepository,
IShapeRepository specializedShapeRepository)
{
_genericShapeRepository = genericShapeRepository;
_specializedShapeRepository = specializedShapeRepository;
}
public void DoSomethingWithTheGenericRepository()
{
IList<Shape> threeSidedShapes =
_genericShapeRepository.FindAll(shape => shape.NumberOfSides == 3).ToList();
_genericShapeRepository.MarkForDeletion(threeSidedShapes[0]);
_genericShapeRepository.SaveAll();
}
public void DoSomethingWithTheSpecializedRepository()
{
IList<Shape> threeSidedShapes =
_specializedShapeRepository.RetrieveByNumberOfSides(3).ToList();
_specializedShapeRepository.MarkForDeletion(threeSidedShapes[0]);
_specializedShapeRepository.SaveAll();
}
}