Skip to Content
Course content

106: The Repository Pattern

Click on the "Edit" button in the top corner of the screen to edit your slide content.

Imagine you're at a massive public library. You need a specific book on C# design patterns. You don't just wander into the basement, start climbing ladders, and manually searching through thousands of Dewey Decimal codes to find the physical shelf where that book sits. That would be a nightmare, and if the library reorganized its shelves tomorrow, you'd be totally lost.

Instead, you go to the librarian. You say, "I need the book on design patterns," and the librarian handles the rest. You don't care if they use a computer, a handwritten ledger, or their own memory to find it. You just want the book. The librarian acts as the mediator between you and the chaotic reality of the bookshelves.

In C#, the Repository Pattern is exactly that librarian. Your business logic (the user) asks for data, and the repository (the librarian) handles the gritty details of how to fetch it from the database (the bookshelves).

Defining the Contract

The secret to making this work is starting with an interface. If we just create a class that talks to a database, we've just moved the mess to a different file. We want an abstraction. Let's say we're building a system to manage a collection of Game objects for a digital storefront.

public interface IGameRepository
{
    Game GetById(int id);
    IEnumerable<Game> GetAll();
    void Add(Game game);
    void Delete(int id);
}

Notice how there is absolutely nothing in here about SQL, Entity Framework, or JSON files. This is the "contract." Any part of your app that needs a game just talks to this interface. It doesn't know—or care—where the games are actually stored.

Hiding the EF Core Mess

Now we actually build the librarian. This is where the "how" happens. I'll use Entity Framework Core here because that's what most of us use, but the beauty is that the rest of your app won't know that.

public class SqlGameRepository : IGameRepository
{
    private readonly GameDbContext _context;

    public SqlGameRepository(GameDbContext context)
    {
        _context = context;
    }

    public Game GetById(int id) => _context.Games.Find(id);

    public IEnumerable<Game> GetAll() => _context.Games.ToList();

    public void Add(Game game)
    {
        _context.Games.Add(game);
        _context.SaveChanges();
    }

    public void Delete(int id)
    {
        var game = _context.Games.Find(id);
        if (game != null)
        {
            _context.Games.Remove(game);
            _context.SaveChanges();
        }
    }
}

I've seen a lot of developers argue that this is "boilerplate" because EF Core's DbSet is already a kind of repository. And they're not entirely wrong. But once you start adding complex filters or custom logic to your queries, your controllers start getting bloated with LINQ expressions. By wrapping this in a repository, you keep that logic in one place.

Why We Actually Bother With This

You might be thinking, "This feels like extra work for no reason." Here is where it pays off: Unit Testing.

If your business logic is hard-coded to use SqlGameRepository, you can't test your logic without a real database running. That's slow and flaky. But because we used IGameRepository, you can create a "Mock" repository in your tests—basically a fake librarian who just returns a list of games from memory. Your tests will run in milliseconds, and you won't have to clean up a database after every run.

Another win? Imagine your boss comes to you in six months and says, "We're moving our game data from SQL Server to MongoDB." If you've used the Repository Pattern, you don't have to touch a single line of code in your business services. You just write a new MongoGameRepository that implements the same interface, swap the registration in your Dependency Injection container, and you're done.




📋 Practical Task

Refactoring the Product Catalog Storage

You've inherited a project for an e-commerce site. Currently, the ProductService class is "fat"—it's directly using a DbContext to perform database queries, making it impossible to unit test without a live database connection.

Your Task:

  1. Create an interface named IProductRepository that defines methods for GetProductById(int id), GetProductsByCategory(string category), and UpdateStock(int id, int newQuantity).
  2. Implement this interface in a class called SqlProductRepository. Assume you have a ProductDbContext available with a DbSet<Product>.
  3. Modify the ProductService class to accept IProductRepository via its constructor instead of the ProductDbContext.
  4. Update the methods in ProductService to call the repository methods instead of querying the context directly.
Rating
0 0

There are no comments for now.

to be the first to leave a comment.