ASP.NET Core Web API

Repository & Unit of Work Pattern

1 Views Updated 5/4/2026

Repository & Unit of Work Pattern

If you inject the DbContext directly into your Controllers to execute LINQ queries, your application has tightly coupled its Web API HTTP logic directly to the Database engine. To achieve Clean Architecture, we decouple these concerns using the Repository Pattern.

1. Why use a Repository Pattern?

A Repository provides an abstraction over database access. Instead of writing EF Core LINQ inside a controller, you call a method like _repo.GetActiveUsers(). It offers two massive advantages:

  • Unit Testability: Contollers can be easily tested without a database by injecting a Mock Repository.
  • Centralized Logic: If the database rule for "Active User" changes, you only update it in one Repository file instead of hunting down LINQ snippets across 50 controllers.

2. Implementing a Standard Repository

// 1. The Interface (The Contract)
public interface IUserRepository
{
    Task<User> GetByIdAsync(int id);
    Task AddAsync(User user);
    Task SaveChangesAsync();
}

// 2. The Implementation (EF Core Specific)
public class UserRepository : IUserRepository
{
    // The DbContext is injected into the Repo, NOT the Controller
    private readonly ApplicationDbContext _context;

    public UserRepository(ApplicationDbContext context)
    {
        _context = context;
    }

    public async Task<User> GetByIdAsync(int id)
    {
        return await _context.Users.FindAsync(id);
    }

    public async Task AddAsync(User user)
    {
        await _context.Users.AddAsync(user);
    }
    
    public async Task SaveChangesAsync()
    {
        await _context.SaveChangesAsync();
    }
}

3. The Unit of Work Extension

What if your API request needs to create a User, add a Billing Record, and send an Audit Log? If you use three different repositories (UserRepository, BillingRepository, AuditRepository) and call SaveChanges() on each of them sequentially, you violate transaction safety. If the Audit fails, the User was already saved!

The Unit of Work Pattern centralizes the SaveChanges() call to a single parent class containing all the repositories. All DB changes happen in one massive transaction.

public interface IUnitOfWork
{
    IUserRepository Users { get; }
    IBillingRepository Billing { get; }
    Task<bool> CompleteAsync();
}

public class UnitOfWork : IUnitOfWork
{
    private readonly ApplicationDbContext _context;

    public IUserRepository Users { get; private set; }
    public IBillingRepository Billing { get; private set; }

    public UnitOfWork(ApplicationDbContext context)
    {
        _context = context;
        Users = new UserRepository(_context);
        Billing = new BillingRepository(_context);
    }

    // Call this exactly once at the end of the Controller Action
    public async Task<bool> CompleteAsync()
    {
        return await _context.SaveChangesAsync() > 0;
    }
}

4. Interview Mastery

Q: "Some senior .NET architects argue that the Repository Pattern is useless when using EF Core. Why do they say that, and when is it actually useful?"

Architect Answer: "They argue that EF Core's `DbSet` IS already a Repository, and `DbContext` IS already a Unit of Work. Creating a wrapper around them is technically 'wrapping a wrapper', resulting in redundant boilerplate. However, the Repository Pattern becomes highly beneficial in Enterprise environments where we use complex Domain-Driven Design (DDD). We use reporitories to enforce aggregate roots—ensuring developers cannot bypass domain rules (like accidentally adding an OrderItem directly to the database without updating the parent Order's TotalPrice). We don't wrap EF Core just for the sake of it; we wrap it to hide `IQueryable` and enforce strict domain business logic."

ASP.NET Core Web API
1. Fundamentals & HTTP
Introduction to ASP.NET Core Web API REST Principles and HTTP Methods Controllers & ControllerBase Routing (Attribute vs Conventional) Action Return Types (IActionResult)
2. Request Handling
Model Binding (FromQuery, FromBody, FromRoute) Dependency Injection (DI) Deep Dive App Settings & The Options Pattern
3. Data Access & Architecture
EF Core Setup in Web API DbContext & Migrations Repository & Unit of Work Pattern Asynchronous Programming (async/await)
4. Data Transfer & Validation
Data Transfer Objects (DTOs) & AutoMapper Model Validation (DataAnnotations) FluentValidation Integration
5. Advanced Concepts
Global Exception Handling Middleware Content Negotiation (JSON vs XML) Pagination & Filtering Advanced Searching & Sorting HATEOAS (Hypermedia) Implementation Output Caching & Response Caching
6. Security & Authorization
Cross-Origin Resource Sharing (CORS) JWT Authentication Setup Access Tokens & Refresh Tokens Workflow Role-Based & Policy-Based Authorization API Key Authentication Rate Limiting & Throttling
7. Documentation & Testing
Swagger & OpenAPI Configuration Customizing API Documentation Unit Testing Controllers (xUnit & Moq) Integration Testing (WebApplicationFactory)
8. Microservices & Deployment
Consuming External APIs (IHttpClientFactory) Health Checks & Diagnostics API Versioning Strategies Deploying APIs (Docker & Azure)