Implementing Repository and Unit of Work Patterns with ASP.NET MVC

Implementing Repository and Unit of Work Patterns with ASP.NET MVC

05 Mar 2024
Advanced
97.1K Views
5 min read

In this article you will learn how to use the repository pattern for CRUD operations and how to combine it with the unit of work patterns. Before going to write the code, let's understand the repository and unit of work patterns separately.

Repository Pattern

The repository pattern is used to create an abstraction layer between the DAL (data access layer) and the BAL (business access layer) to perform CRUD operations.

Repository Pattern

Repository Pattern Implementation

The repository pattern can be implemented by using following two method :

  1. Generic Repository Pattern

    A generic repository implementation is used to define common database operations (like Create, Retrieve Update, Delete etc.) for all the database entities in a single class.

    public interface IRepository where TEntity : class
    {
     IEnumerable GetAll();
     IEnumerable Find(Expression> predicate);
     TEntity Get(object Id);
    
     void Add(TEntity entity);
     void AddRange(IEnumerable entities);
    
     void Update(TEntity entity);
    
     void Remove(object Id);
     void Remove(TEntity entity);
     void RemoveRange(IEnumerable entities);
    }
    
    // implementation
    public class Repository : IRepository where TEntity : class
    {
     protected readonly DbContext db;
    
     public Repository(DbContext _db)
     {
     db = _db;
     }
    
     public IEnumerable GetAll()
     {
     return db.Set().ToList();
     }
    
     public IEnumerable Find(Expression> predicate)
     {
     return db.Set().Where(predicate);
     }
    
     public TEntity Get(object Id)
     {
     return db.Set().Find(Id);
     }
    
     public void Add(TEntity entity)
     {
     db.Set().Add(entity);
     }
    
     public void AddRange(IEnumerable entities)
     {
     db.Set().AddRange(entities);
     }
    
     public void Remove(TEntity entity)
     {
     db.Set().Remove(entity);
     }
    
     public void RemoveRange(IEnumerable entities)
     {
     db.Set().RemoveRange(entities);
     }
    
     public void Remove(object Id)
     {
     TEntity entity = db.Set().Find(Id);
     this.Remove(entity);
     }
    
     public void Update(TEntity entity)
     {
     db.Entry(entity).State = EntityState.Modified;
     }
    }
    
  2. Non-Generic Repository Pattern (Specific Repository)

    A non-generic repository implementation is used to define all database operation related to an entity within a separate class. For example, if you have two entities Category and Product, each entity will have its own implementation repository.

    public interface IProductRepository
    {
     IEnumerable GetAll();
     IEnumerable Find(Expression> predicate);
     Product Get(int Id);
    
     void Add(Product entity);
     void AddRange(IEnumerable entities);
    
     void Update(Product entity);
    
     void Remove(int Id);
     void Remove(Product entity);
     void RemoveRange(IEnumerable entities);
    }
    
    // implementation
    public class ProductRepository
    {
     protected readonly DbContext db;
    
     public Repository(DbContext _db)
     {
     db = _db;
     }
    
     public IEnumerable GetAll()
     {
     return db.Products.ToList();
     }
    
     public IEnumerable Find(Expression> predicate)
     {
     return db.Products.Where(predicate);
     }
    
     public Product Get(int Id)
     {
     return db.Products.Find(Id);
     }
    
     public void Add(Product entity)
     {
     db.Products.Add(Product);
     }
    
     public void AddRange(IEnumerable entities)
     {
     db.Products.AddRange(entities);
     }
    
     public void Remove(Product entity)
     {
     db.Products.Remove(entity);
     }
    
     public void RemoveRange(IEnumerable entities)
     {
     db.Products.RemoveRange(entities);
     }
    
     public void Remove(object Id)
     {
     Product entity = db.Products.Find(Id);
     this.Remove(entity);
     }
    
     public void Update(Product entity)
     {
     db.Entry(entity).State = EntityState.Modified;
     }
    }
    

Recommended Repository Pattern Implementation

If you will use one of the above implementation, generic you can not use for specific operation on an entity and in case of non-generic implementation, you have to write code for common CRUD operations for each entity. So better way is, just create a generic repository for commonly used CRUD operation and for specific one create a non-generic repository and inherit form generic repository. The example code is given below:

public interface IProductRepository : IRepository
{
 IEnumerable GetProductsByCategory(int id);
}
 
public class ProductRepository : Repository, IProductRepository
{
 public DatabaseContext context
 {
 get
 {
 return db as DatabaseContext;
 }
 }

 public ProductRepository(DatabaseContext _db) : base(_db)
 {

 }
 
 public IEnumerable GetProductsByCategory(int id)
 {
 return context.Products.Where(p => p.CategoryId == id).ToList();
 }
} 

Unit of work Pattern

The unit of pattern implementation manage in-memory database CRUD operations on entities as one transaction. So, if one of the operation is failing then entire db operations will be rollback.

Unit of work Pattern
 
public interface IUnitOfWork : IDisposable
{
 ICategoryRepository Categories { get; }
 IProductRepository Products { get; }

 int SaveChanges();
}

public class UnitOfWork : IUnitOfWork
{
 private readonly DatabaseContext db;

 public UnitOfWork()
 {
 db = new DatabaseContext();
 }

 private ICategoryRepository _Categories;
 public ICategoryRepository Categories
 {
 get
 {
 if (this._Categories == null)
 {
 this._Categories = new CategoryRepository(db);
 }
 return this._Categories;
 }
 }

 private IProductRepository _Products;
 public IProductRepository Products
 {
 get
 {
 if (this._Products == null)
 {
 this._Products = new ProductRepository(db);
 }
 return this._Products;
 }
 }

 public int SaveChanges()
 {
 return db.SaveChanges();
 }

 public void Dispose()
 {
 db.Dispose();
 }
}

Entity Framework and Repository and Unit Of Work Patterns

Entity Framework is based on the repository and unit of work patterns to perform CRUD operations on an entity.

Entity Framework and Repository and Unit Of Work Patterns

The DbSet class is based on the repository design pattern which provides us a set of method to perform CRUD operations on an entity. The DbContext class is based on unit of work pattern which includes all the DbSet entities. The DbContext class manages in-memory database operations on these entities and later saves all these updates as one transaction into database.

Advantages of Repository and Unit Of Work Design Patterns

  1. Abstract Data Access Layer and Business Access Layer from the Application.

  2. Manage in-memory database operations and later saves in-memory updates as one transaction into database.

  3. Facilitates to make the layers loosely-coupled using dependency injection.

  4. Facilitates to follow unit testing or test-driven development (TDD).

What do you think?

I would like to have feedback from my blog readers. Your valuable feedback, question, or comments about this article are always welcome.

Share Article
About Author
Shailendra Chauhan (Microsoft MVP, Founder & CEO at Scholarhat by DotNetTricks)

Shailendra Chauhan is the Founder and CEO at ScholarHat by DotNetTricks which is a brand when it comes to e-Learning. He provides training and consultation over an array of technologies like Cloud, .NET, Angular, React, Node, Microservices, Containers and Mobile Apps development. He has been awarded Microsoft MVP 9th time in a row (2016-2024). He has changed many lives with his writings and unique training programs. He has a number of most sought-after books to his name which has helped job aspirants in cracking tough interviews with ease.
Accept cookies & close this