Introduction and Implementation
The repository pattern belongs to the family of design patterns. It's used for handling common data access. It's easily maintainable and highly testable. Besides that, Unit Of Work can be seen as transaction to ensure that all operations in an unit are successfully executed. If one of them is failed, nothing happens.
In C#, there is an interface called IQueryable. It is like lazy interface. When called, it will be executed. This interface fits to Repository pattern well.
First, we define a new class EntityBase with Id and Version.
public class EntityBase
{
public int Id { get; set; }
public byte[] Version { get; set; }
}
The Version property can help to check concurrency in database. Nowadays, most of ORMs support concurrency check automatically.
Second, we define a new interface IRepository where its constraint of T is EntityBase. There, we will write 5 methods: List, Create, Read, Update and Delete. This can call LCRUD.
public interface IRepository<T> where T: EntityBase
{
IQueryable<T> List();
void Create(T entity);
T Read(object keys);
void Update(T entity);
void Delete(T entity);
}
Third, we implement a new class GenericRepository using DbContext in Entity Framework.
public abstract class GenericRepository<T> : IRepository<T>
where T: EntityBase
{
private readonly DbContext _dbContext;
public GenericRepository(DbContext dbContext)
{
this._dbContext = dbContext;
}
public virtual IQueryable<T> List()
{
return _dbContext.Set<T>();
}
public virtual void Create(T entity)
{
_dbContext.Set<T>().Add(entity);
}
public virtual T Read(object keys)
{
return _dbContext.Set<T>().Find(keys);
}
public virtual void Update(T entity)
{
_dbContext.Entry(entity).State = EntityState.Modified;
}
public virtual void Delete(T entity)
{
_dbContext.Set<T>().Remove(entity);
}
}
Lastly, we create a new class UnitOfWork.
public sealed class UnitOfWork : IDisposable
{
private readonly DbContext _dbContext;
public UnitOfWork(DbContext dbContext)
{
this._dbContext = dbContext;
// TODO: Initialize repositories here...
// e.g. this.StudentRepository
// = new StudentRepository(dbContext);
}
public bool Save()
{
bool isSuccess = _dbContext.SaveChanges() > 0;
return isSuccess;
}
public void Dispose()
{
if (_dbContext == null) return;
_dbContext.Dispose();
}
// e.g. public readonly IStudentRepository StudentRepository;
}
Usage
- Each entity must inherit EntityBase.
- For each entity, you must create a new interface I[xxx]Repository and a new class [xxx]Repository inheriting GenericRepository and implementing I[xxx]Repository.
- New Repository classes have to be initialized in UnitOfWork class.
Best Practices:
We can take advantage of IoC frameworks to register DbContext and UnitOfWork into services container and then use UnitOfWork as constructor parameter.
Samples
Suppose StudentDbContext is provided and Ninject is used for registering two services UnitOfWork and StudentDbContext. UnitOfWork will be automatically injected in Studentcontroller.
public class StudentController : ControllerBase
{
private readonly UnitOfWork _unitOfWork;
public StudentController(UnitOfWork unitOfWork)
{
this._unitOfWork = unitOfWork;
}
}
A Console application is to add two new Students to database.
public class Program
{
static void Main(params string [] args)
{
DbContext studentDbContext = new StudentDbContext();
using(UnitOfWork unitOfWork = new UnitOfWork(studentDbContext))
{
unitOfWork.StudentRepository
.Create(new Student("Larry", "Page"))
unitOfWork.StudentRepository
.Create(new Student("Mark", "Zuckerberg"))
unitOfWork.Save();
}
}
}
Hopefully, this post can help you to implement your own repository and unit of work.
Happy coding :)
Top comments (8)
I think you meant virtual instead of
overridable
, which isn't a valid keyword in C#.Thanks for pointing out. I fixed that :). The "overridable" keyword belongs to VB.NET. At the time of writing, I have written some VB.NET code as well.
This is a short and direct to the point explanation of Unit of Work and Repository pattern, your article was very helpful. Thank you for sharing.
Thanks.
Great work :)
Thanks!
This is an awesome article! There's one thing I don't understand, why don't you let repositories be injected into UnitOfWork?
Thanks!
A good question :). However, integrating dependency injection is out of scope of this post.
A couple of following steps could help you extend UnitOfWork.