The Powerful Repository Pattern In C# Explained

by Kenji Elzerman
Repository Pattern in C# - Kens Learning Curve

In earlier articles, I spoke about the strategy pattern and the decorator pattern. While these are pretty popular, one pattern is even more popular than these two combined: The repository pattern. Especially in C#, this is a good pattern to use when you are working with databases. So, let’s discover the repository pattern in C#.

Goals

After reading this article you

  • Understand the idea behind the repository pattern
  • Realize why it can help you
  • Create a repository pattern
  • Learned the differences between IEnumerable and IQueryable
  • Can unit test with a repository pattern using mocking

The Idea Behind The Repository Pattern

In short: The repository pattern can decouple the data layer from the business layer. This way you don’t need to worry about all kinds of specific logic for the database. The repository pattern has ‘simple’ methods, like create, delete, get, and more. Nothing fancy. It contains little to no logic.

Another thing the repository pattern can take care of is the connections, commands, and stuff. In C# you open a connection to the database, execute a command, close the connection, and carry on with your data. Without the repository pattern, you can have a lot of places where you manage those connections and commands.

There are situations where you can have multiple databases or even different database types, like MySQL, MSSQL, Oracle, and so on. With a well-built repository pattern, you don’t have to change the business logic, as long as the interface of a repository pattern doesn’t change.

I like to compare the repository pattern with something like Amazon. When I order something at Amazon I sometimes don’t order directly from Amazon but from a third-party shop, which sells the stuff through Amazon. Frankly, I don’t care who sells it, as long as it arrives at my door. So, Amazon is the repository. I ask for a specific product and Amazon makes sure it’s paid, sent, and delivered to my doorstep. 

I will show you how to create a repository pattern for Entity Framework Core.

It All Started With An API

Before we can create any form of repository pattern in C#, we need a solution with a project. I have created a minimal web API with C# and .NET 7. You can download the source from my GitHub repository here:

https://github.com/KensLearningCurve/RepositoryPatternDemo

Just keep in mind there are two branches: Main is the start source code for this tutorial. The other branch is called ‘Finished’, which contains all the source code with the repository pattern as explained in this article.

Let’s walk through the source code in the main branch. There is an API with the name RepositoryPatternDemo.API. This is your typical .NET 7 minimal API. The most important stuff is in the Program.cs, which has all the configurations for the data context (Entity Framework Core), dependency injection, and mappings.

The next project is the class library called RepositoryPatternDemo.Business, which has all the logic that the API (or any other front-end type) can use. This project also has a connection with the database through Entity Framework Core, as can be seen in the file DataContext.cs. The classes MovieService and GenreService contain the logic to handle all operations regarding movies and genres. These classes are injected into the mappings.

The third and last project is the class library RepositoryPatternDemo.Domain, which contains the interfaces and entities that are shared throughout other projects. Nothing special here.

Note that I am using the 3-tier architecture in this project. I think (personal opinion) this is really important to keep all stages of your application code separated to maintain structure and control.

The Problem

This whole solution is built without any thought of the repository pattern. If you take a look at the services in the business, you will see a lot of duplicate code. The Create and Delete methods are almost identical, except for the entity. What if something changes with the context? I have to change the change on multiple locations. This is one of the things we will fix with the repository pattern.

In other words: The code to access the database is in the logic itself and we don’t want that.

Create A Repository

A repository in C# consists of a class and an interface. We will create an interface with the needed methods.

Interface

I will add the interface IMovieRepository in a folder called Repositories in the RepositoryPatternDemo.Business project. 

The name of the repository is important. IMovieRepository tells me that this is the repository for the entity movie.

The methods inside the interface are just the CRUD methods. That’s it. No other types of methods are needed here. Notice that no mention of Entity Framework is needed here.

One thing we can add is the IDisposable. This interface will add the Dispose method, which will be called as soon as the repository class is done and disposed of.

public interface IMovieRepository: IDisposable
{
    void Create(Movie movie);
    IEnumerable<Movie> Get();
    Movie? Get(int id);
    void Update(Movie movie);
    void Delete(int id);
    void SaveChanges();
}

Interface Implementation

Next up is the implementation of the interface. I will create a class named MovieRepository and save it in the same folder as the interface. Then I will connect the IMovieRepository interface to the new class and implement the methods.

Since we want to use Entity Framework I need to inject the data context. This means we need a constructor in the MovieRepository:

private readonly DataContext dataContext;

public MovieRepository(DataContext dataContext)
{
    this.dataContext=dataContext;
}

Now we can implement the other methods. Just note that we don’t want any logic inside the repository, or at least as little as possible. Checks on properties should be done in the business layer or in the front end, like the API.

public class MovieRepository : IMovieRepository
{
    private readonly DataContext dataContext;
    private bool disposed = false;

    public MovieRepository(DataContext dataContext)
    {
        this.dataContext=dataContext;
    }

    public void Create(Movie movie)
    {
        dataContext.Movies.Add(movie);
    }

    public void Delete(int id)
    {
        dataContext.Movies.Remove(Get(id));
    }

    public IEnumerable<Movie> Get()
    {
        return dataContext.Movies;
    }

    public Movie? Get(int id)
    {
        return dataContext.Movies.Find(id); ;
    }

    public void Update(Movie movie)
    {
        dataContext.Entry(movie).State = EntityState.Modified;
    }

    public void SaveChanges()
    {
        dataContext.SaveChanges();
    }

    protected virtual void Dispose(bool disposing)
    {
        if (!disposed)
            if (disposing)
                dataContext.Dispose();

        disposed = true;
    }

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }
}

If you look at the code above you might think “But… That’s what was in de MovieService.cs!” and you are correct. Well, most of it. I didn’t copy-paste the validation checks (yes, I literally copy-paste the code).

Plus, I have added the implementation for the IDisposable interface. This one will dispose of the data context when needed. It will cause to close any connections open in the dbContext.

Notice that I have added a method SaveChanges() explicitly and not called dataContext.SaveChanges() each time I would need it. This is because in some cases you want to queue database actions and execute them all at once with the SaveChanges()

All done? Well, no. We also need genres and we don’t have a repository for genres. Let’s create the repository for the genres.

My advice: Duplicate the MovieRepository (including the interface), rename them, and change the code to the genres. The interface and GenreRepository should look like this:

// Interface:
public interface IGenreRepository: IDisposable
{
    void Create(Genre movie);
    IEnumerable<Genre> Get();
    Genre? Get(int id);
    void Update(Genre movie);
    void Delete(int id);
}

// Implementation:
public class GenreRepository : IGenreRepository
{
    private readonly DataContext dataContext;
    private bool disposed = false;

    public GenreRepository(DataContext dataContext)
    {
        this.dataContext=dataContext;
    }

    public void Create(Genre genre)
    {
        dataContext.Genres.Add(genre);
    }

    public void Delete(int id)
    {
        dataContext.Genres.Remove(Get(id));
    }

    public IEnumerable<Genre> Get()
    {
        return dataContext.Genres;
    }

    public Genre? Get(int id)
    {
        return dataContext.Genres.Find(id); ;
    }

    public void Update(Genre genre)
    {
        dataContext.Entry(genre).State = EntityState.Modified;
    }

    public void SaveChanges()
    {
        dataContext.SaveChanges();
    }

    protected virtual void Dispose(bool disposing)
    {
        if (!disposed)
            if (disposing)
                dataContext.Dispose();

        disposed = true;
    }

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }
}

Using The Repository

Now that we have created the repositories we can use it in the MovieService.cs. Just add the interfaces in the constructor, so you inject the implementation, remove the data context, and fix the code in the methods. Simple, right?

public class MovieService : IMovieService
{
    private readonly IMovieRepository movieRepository;
    private readonly IGenreRepository genreRepository;

    public MovieService(IMovieRepository movieRepository, IGenreRepository genreRepository)
    {
        this.movieRepository=movieRepository;
        this.genreRepository=genreRepository;
    }

    public void Create(Movie movie)
    {
        if (movie==null) throw new ArgumentNullException(nameof(movie));

        Genre selectedGenre = genreRepository.Get(movie.GenreId) ?? throw new Exception("Genre not found");

        movie.Genre = selectedGenre;

        movieRepository.Create(movie);    
    }

    public void Delete(int id)
    {
        if (id <= 0) throw new ArgumentOutOfRangeException(nameof(id));

        movieRepository.Delete(id);
    }

    public Movie? Get(int id) => movieRepository.Get().Include(x => x.Genre).SingleOrDefault(x => x.Id == id) ?? null;

    public IEnumerable<Movie> Get() => movieRepository.Get();

    public IEnumerable<Movie> Get(string query) => movieRepository.Get().Include(x => x.Genre).Where(x => x.Title.Contains(query));

    public void Update(Movie movie)
    {
        movieRepository.Update(movie);
    }
}

Errors

The above code is correct, but it gives compile errors. Especially on the lines that use the Include. The reason is simple: IEnumerable doesn’t have a definition for include. But how can we include the Genre?

As you might know, IEnumerable is just an interface that is the base of lists. This interface is also used in other implementations, like the IQueryable. And it’s this interface that supports the Include().

So, open the IMovieRespository and change the method IEnumerable<Movie> Get() to IQueryable<Movie> Get(). Don’t forget to change the implementation in MovieService!

IEnumerable VS IQueryable

But what is the difference? Simply put: The IQueryable is perfect for querying data from a database or a service, which are out-memory collections. The IEnumerable is better for in-memory collections.

IQueryable is also more suitable for LINQ to SQL queries. IEnumerable is better for LINQ to Object. 

There are many other differences and I don’t want to bore you with stuff you aren’t looking for. A simple Google search will give you all the differences between IEnumerable and IQueryable. Just remember that the biggest advantage of the IQueryable is that it’s better for querying databases.

Unit Testing

Not sure if you ever had a situation where you needed to create unit tests while using a data context such as in the example code. This can be a real hassle, especially when the data context is injected into the service, like the MovieService.

Mocking a data context isn’t as easy as it sounds and it took me a while to figure it out. But if you use repositories in C# the testing and mocking get a lot easier.

I wrote extensive tutorials about unit testing in C# and also on mocking. If you want in-depth information about unit testing and mocking I strongly suggest checking out those tutorials.

Remember that the repository doesn’t get any logic. It simply retrieves or sends data from or to the data source. Because of this, we don’t test the implementation of the repository. Our lives suddenly got a lot easier.

The repositories (GenreRepository and MovieRepository) both have an interface. We can mock those and take control of the public methods.

I will show an example of how you can mock and use the repository in C# with unit tests. In the finished example, you will find all the unit tests. 

Let’s test MovieService.Get(). A pretty simple method that just returns the movies from the data source. I created a new class inside the test project (XUnit by the way). Next, I create the test method called Should_ReturnFiveMovies(). How do I know it’s 5 movies? Because I am going to make sure the mock returns 5 movies. Let’s initialize the MovieService.

Mocking A Repository

public class Get
{
    [Fact]
    public void Should_ReturnFiveMovies()
    {
        MovieService classUnderTest = new();
    }
}

This will give an error on the new(), because it’s expecting two parameters: IMovieRepository and IGenreRepository. Let’s mock these and place them in the initialization of MovieService:

public class Get
{
    [Fact]
    public void Should_ReturnFiveMovies()
    {
        Mock<IGenreRepository> mockGenreRepository = new();
        Mock<IMovieRepository> mockMovieRepository = new();

        MovieService classUnderTest = new(mockMovieRepository.Object, mockGenreRepository.Object);
    }
}

With that done we can create the actual test. I want to call the Get() on the MovieService and retrieve 5 movies. This is pretty simple:

public class Get
{
    [Fact]
    public void Should_ReturnFiveMovies()
    {
        Mock<IGenreRepository> mockGenreRepository = new();
        Mock<IMovieRepository> mockMovieRepository = new();

        MovieService classUnderTest = new(mockMovieRepository.Object, mockGenreRepository.Object);

        List<Movie> result = classUnderTest.Get().ToList();

        Assert.Equal(5, result.Count);
    }
}

But when you run this the test will fail, because the result isn’t 5, it’s 0. This is because we didn’t tell the mocked version of the IMovieRepository to return 5 movies.

Mock SetUp

[Fact]
public void Should_ReturnFiveMovies()
{
    Mock<IGenreRepository> mockGenreRepository = new();
    Mock<IMovieRepository> mockMovieRepository = new();

    mockMovieRepository.Setup(x => x.Get()).Returns(new List<Movie>
    {
        new Movie { Id = 1 },
        new Movie { Id = 2 },
        new Movie { Id = 3 },
        new Movie { Id = 4 },
        new Movie { Id = 5 },
    }.AsQueryable);

    MovieService classUnderTest = new(mockMovieRepository.Object, mockGenreRepository.Object);

    List<Movie> result = classUnderTest.Get().ToList();

    Assert.Equal(5, result.Count);
}

If you run the test now, you will have a positive test.

Notice the AsQueryable on line 14. You can’t initialize an IQueryable. So to make the mock return an IQueryable I create a List of movies and convert it to a  Queryable with the AsQueryable.

Conclusion

And that concludes an introduction to the repository pattern in C#. There is much more to tell you about this pattern and how to use it. For example, you can make the repository generic. This way you don’t have to create a repository per entity.

Another example of what you could do with the repository pattern is change the data source without changing any logic in the business. I briefly mentioned it, but it’s a lot of information to place it in one, single tutorial. So keep an eye on the new tutorials.

Leave a Comment

* By using this form you agree with the storage and handling of your data by this website.

Table Of Contents
Kens Learning Curve