DEV Community

1001binary
1001binary

Posted on • Updated on

Implement Repository Base and Unit of Work in C#

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; }
}
Enter fullscreen mode Exit fullscreen mode

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);
}
Enter fullscreen mode Exit fullscreen mode

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);
   }
}
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

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;
   }
}
Enter fullscreen mode Exit fullscreen mode

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();
      }

   }
}
Enter fullscreen mode Exit fullscreen mode

Hopefully, this post can help you to implement your own repository and unit of work.

Happy coding :)

Discussion (8)

Collapse
seangwright profile image
Sean G. Wright

I think you meant virtual instead of overridable, which isn't a valid keyword in C#.

Collapse
1001binary profile image
1001binary Author

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.

Collapse
dyagzy profile image
dyagzy

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.

Collapse
1001binary profile image
1001binary Author

Thanks.

Collapse
filatovv profile image
Yuri Filatov

Great work :)

Collapse
1001binary profile image
1001binary Author

Thanks!

Collapse
sixpeteunder profile image
pete

This is an awesome article! There's one thing I don't understand, why don't you let repositories be injected into UnitOfWork?

Collapse
1001binary profile image
1001binary Author

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.

  • Choose one of best .NET IoCs (Ninject, Autofac,...) for your needs. If you are using ASP.NET Core, simply skip this step.
  • Register UnitOfWork, StudentDbContext, repositories and services.
  • Remove The "dbContext" constructor parameter from UnitOfWork.
  • Inject repositories and services into UnitOfWork.