Repository & Unit of Work

Repository Pattern

What:
  • The Repository pattern is a design pattern that acts as an abstraction layer between your application's business logic and the data access layer (DAL).
  • It encapsulates the logic needed to access data sources (such as databases) and provides a collection-like interface for managing domain entities.
  • It helps to isolate and decouple data access code from business logic.
Why use it:
  • Separation of concerns: Keeps your business logic independent from data access details.
  • Testability: Makes it easier to mock or stub data access in unit tests.
  • Maintainability: Centralizes CRUD operations and common queries for entities.
  • Flexibility: Allows switching data sources or ORMs without affecting business logic.
How it works:
  • Typically, you define repository interfaces with methods like AddUpdateDeleteGetByIdGetAll etc.
  • Concrete repository classes implement these interfaces using the specific data access technology (e.g., Entity Framework).
Example:
public interface IProductRepository { Task<Product> GetByIdAsync(int id); Task<IEnumerable<Product>> GetAllAsync(); void Add(Product product); void Update(Product product); void Delete(Product product); } public class ProductRepository : IProductRepository { private readonly DbContext _context; public ProductRepository(DbContext context) => _context = context; // Implement methods using EF DbSets }

Unit of Work Pattern

What:
  • The Unit of Work (UoW) pattern maintains a list of objects affected by a business transaction and coordinates the writing out of changes as a single operation (transaction).
  • It ensures that multiple operations across one or more repositories are committed atomically, so either all succeed or all fail (rollback on error).
  • In .NET's Entity Framework, the DbContext itself implements UoW internally.
Why use it:
  • Consistency: Guarantees that related changes in multiple repositories are saved together.
  • Transaction management: Helps manage database transactions at a higher level than individual repositories.
  • Simplification: Aggregates multiple repository operations into a single commit point.
  • Reduced coupling: Instead of directly saving changes per repository, the UoW handles the commit lifecycle.
How it works:
  • UoW exposes access to repositories as properties.
  • It provides a SaveChanges() or Commit() method to persist changes.
  • All repositories share the same database context or transaction scope.
Example:
public interface IUnitOfWork : IDisposable { IProductRepository Products { get; } ICategoryRepository Categories { get; } int SaveChanges(); } public class UnitOfWork : IUnitOfWork { private readonly DbContext _context; public UnitOfWork(DbContext context) => _context = context; // Lazy-loaded repository instances public IProductRepository Products { get; } public ICategoryRepository Categories { get; } public int SaveChanges() => _context.SaveChanges(); public void Dispose() => _context.Dispose(); }

Can Repository and Unit of Work be used together or are they different?

  • Yes, they are complementary and often used together.
  • Repository abstracts data access for an entity or aggregate providing CRUD and query methods.
  • Unit of Work coordinates multiple repositories and manages transactions, committing all changes in a single atomic operation.
  • In Entity Framework, the DbContext acts as both repository (via DbSet) and unit of work (transaction context), but creating your own repositories and UoW adds abstraction and testability.
  • Using both together provides clean separation, easier testing, and transaction management across multiple repositories.

Other Common Database-Related Patterns to Know

  1. Generic Repository Pattern
      • A generic implementation of repository methods (AddUpdateDeleteGet) for most entities to avoid repetition.
      • Can be combined with specific repositories for entity-specific queries.
  1. Specification Pattern
      • Encapsulates query criteria in reusable specification objects.
      • Helps build complex queries easily and keeps repository methods simple.
  1. Data Mapper Pattern
      • Separates in-memory objects from database schema mapping.
      • ORM libraries like Entity Framework implement this pattern.
  1. Identity Map
      • Ensures each object is loaded only once per transaction to avoid duplicate loading and maintain consistency.
  1. Lazy Loading / Eager Loading Patterns
      • Related to how related entities are fetched (on demand or upfront).
  1. Transaction Script Pattern
      • Organizes business logic into scripts or services; simpler alternative to domain-driven approaches combining with repositories.

Summary Table

Pattern
Purpose
Relationship
Repository
Provides abstraction to CRUD and queries for entities
Focuses on entity-level data access
Unit of Work
Manages transaction and coordination of multiple repositories
Coordinates repositories and saves work
Generic Repository
Reusable generic data access logic
Simplifies repository implementations
Specification
Encapsulates query criteria in objects
Used alongside Repository
Data Mapper
Maps between database and in-memory objects
Underlies Entity Framework and ORMs
Identity Map
Ensures single object instance per transaction
Often implemented inside ORM

Summary

  • The Repository pattern abstracts data access per entity or aggregate.
  • The Unit of Work pattern manages a set of changes across repositories and commits them transactionally.
  • They are often used together but serve distinct roles.
  • Additional patterns like Generic Repository, Specification, and Data Mapper improve the flexibility and maintainability of data access.
  • Entity Framework internally implements Repository and Unit of Work, but creating your own abstractions is a best practice for larger or testable applications.

References