DEV Community

Cover image for Repository Pattern Explained Without Confusion
Prince Patel
Prince Patel

Posted on

Repository Pattern Explained Without Confusion

Have you ever written database queries directly inside your controllers and later wondered why your code became difficult to maintain?

I made the same mistake when I first started learning ASP.NET Core. At first, everything worked fine. But as my project grew, controllers became bloated, database logic was scattered everywhere, and making changes became frustrating.

That's when I discovered the Repository Pattern—a simple design pattern that separates data access from business logic, making applications cleaner, easier to maintain, and much easier to test.

In this article, I'll explain the Repository Pattern in the simplest way possible—with real examples, diagrams, and practical ASP.NET Core code.


What You'll Learn

By the end of this article, you'll understand:

  • What the Repository Pattern is
  • Why developers use it
  • Problems it solves
  • How it fits into an ASP.NET Core application
  • How to implement it step by step
  • Advantages and disadvantages
  • Common mistakes to avoid
  • Best practices

Let's get started.


The Problem

When beginners start building APIs, it's common to write code like this:

public class ProductController : ControllerBase
{
    private readonly AppDbContext _context;

    public ProductController(AppDbContext context)
    {
        _context = context;
    }

    [HttpGet]
    public IActionResult GetProducts()
    {
        return Ok(_context.Products.ToList());
    }
}
Enter fullscreen mode Exit fullscreen mode

At first glance, this looks perfectly fine.

So what's wrong?

The controller is directly communicating with the database.

This creates several problems:

  • The controller now has two responsibilities.
  • Business logic and data access become mixed together.
  • Unit testing becomes difficult.
  • Any database-related changes require modifications inside the controller.
  • As your application grows, controllers become large and difficult to maintain.

In small projects this might not matter.

In large projects, it becomes a nightmare.


What is the Repository Pattern?

The Repository Pattern acts as a middle layer between your application and the database.

Instead of allowing controllers to communicate directly with Entity Framework, controllers interact with a Repository, and the repository handles all database operations.

This creates a clear separation of responsibilities.


A Simple Analogy

Imagine you're eating at a restaurant.

  • Customer → Controller
  • Waiter → Repository
  • Kitchen → Database

The customer never walks into the kitchen to cook food.

Instead, they tell the waiter what they want.

The waiter communicates with the kitchen and returns with the food.

The Repository works exactly the same way.

The controller simply asks the repository for data, and the repository communicates with the database.


Architecture

Client
   │
   ▼
Controller
   │
   ▼
Service
   │
   ▼
Repository
   │
   ▼
DbContext
   │
   ▼
Database
Enter fullscreen mode Exit fullscreen mode

Each layer has a single responsibility:

Layer Responsibility
Controller Handle HTTP requests and responses
Service Business logic
Repository Database operations
DbContext Communicate with Entity Framework
Database Store application data

This separation makes the application much easier to maintain.


Step 1: Create the Repository Interface

The interface defines the operations our repository should support.

public interface IProductRepository
{
    Task<List<Product>> GetAllAsync();
    Task<Product?> GetByIdAsync(int id);
    Task AddAsync(Product product);
    Task UpdateAsync(Product product);
    Task DeleteAsync(int id);
}
Enter fullscreen mode Exit fullscreen mode

Using an interface allows us to change the implementation later without affecting the controller.


Step 2: Implement the Repository

Now let's implement the interface.

public class ProductRepository : IProductRepository
{
    private readonly AppDbContext _context;

    public ProductRepository(AppDbContext context)
    {
        _context = context;
    }

    public async Task<List<Product>> GetAllAsync()
    {
        return await _context.Products.ToListAsync();
    }

    public async Task<Product?> GetByIdAsync(int id)
    {
        return await _context.Products.FindAsync(id);
    }

    public async Task AddAsync(Product product)
    {
        await _context.Products.AddAsync(product);
        await _context.SaveChangesAsync();
    }

    // UpdateAsync() and DeleteAsync() would follow the same pattern.
}
Enter fullscreen mode Exit fullscreen mode

Notice that all database-related code now lives inside the repository instead of the controller.


Step 3: Register Dependency Injection

Register the repository in Program.cs.

builder.Services.AddScoped<IProductRepository, ProductRepository>();
Enter fullscreen mode Exit fullscreen mode

Now ASP.NET Core knows which implementation to provide whenever IProductRepository is requested.


Step 4: Use the Repository in the Controller

Now our controller becomes much cleaner.

public class ProductController : ControllerBase
{
    private readonly IProductRepository _repository;

    public ProductController(IProductRepository repository)
    {
        _repository = repository;
    }

    [HttpGet]
    public async Task<IActionResult> GetProducts()
    {
        return Ok(await _repository.GetAllAsync());
    }
}
Enter fullscreen mode Exit fullscreen mode

The controller no longer needs to know anything about DbContext or Entity Framework.

Its only responsibility is handling HTTP requests.


Benefits

Using the Repository Pattern provides several advantages.

  • Cleaner code structure
  • Better separation of concerns
  • Easier unit testing
  • Reusable data access logic
  • Easier maintenance
  • Better scalability
  • Reduced code duplication

Common Mistakes

Avoid these common mistakes:

Putting business logic inside repositories
Returning IQueryable everywhere
Injecting both DbContext and the repository into the same controller
Creating one giant repository for every entity

Remember:

  • Repository = Data Access
  • Service = Business Logic
  • Controller = HTTP Requests

Best Practices

  • Keep repositories focused only on database operations.
  • Keep business logic inside the Service layer.
  • Use asynchronous methods whenever possible.
  • Program against interfaces instead of concrete classes.
  • Use Dependency Injection.
  • Keep repositories small and focused.

When Should You Use the Repository Pattern?

Use it when:

  • You're building medium or large applications.
  • Multiple developers are working on the project.
  • You want clean architecture.
  • You need unit testing.
  • You expect the project to grow.

For very small CRUD applications or quick prototypes, directly using DbContext may be sufficient.

Choose the level of abstraction that fits your project's complexity.


Conclusion

The Repository Pattern isn't about writing more code—it's about writing better-organized code.

By separating database operations from controllers, your application becomes cleaner, easier to test, and much easier to maintain as it grows.

If you're serious about becoming an ASP.NET Core developer, understanding the Repository Pattern is an important step toward writing production-ready applications.


What do you think?

Do you use the Repository Pattern in your ASP.NET Core projects?
Or do you prefer working directly with DbContext?
Let me know in the comments—I’d love to hear your thoughts.
If you found this article helpful, consider leaving a love and following me for more beginner-friendly ASP.NET Core tutorials.

Next in this series: Dependency Injection Explained Without Confusion

dotnet #aspnetcore #csharp #webdev

Top comments (0)