Refactoring database calls out of a controller using the repository pattern

At some point you’ll find yourself rushing to complete a feature and end up sacrificing quality.

I found an example of this in an old article of mine from a since deceased website. The examples are somewhat timeless so I decided to give it a second life.

In haste, I had injected my Entity Framework DbContext directly into my API controllers and did all of the database work right in the controller methods themselves.

By doing that, I had created a direct dependency on Entity Framework by the API. That’s not always a big problem if we know we will always use EF, but it is usually good to program to an interface to abstract away the database technology and allow for easier future changes, maintainability, test mocking, and a more readable API.

This post will walk through the refactoring of one of the API controllers. We will:

  • Move the database code out of the controller and into a repository class
  • Let aspnetcore handle injecting the concrete repository class into the controller
  • Program to the repository interface

Now, about that Repository Pattern…

I know, I know… Many of you quiver when Entity Framework and Repository Pattern are mentioned in the same sentence. We’re going to do it anyway because in our case, we don’t need the advanced features from EF that you might lose by abstracting it away.

Let’s take a look at the current state of the controller…

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
namespace MyApp.WebApi.Controllers
{
    [Route("api/[controller]")]
    public class BookmarksController : Controller
    {

        private readonly BookmarkContext _dbContext;

        public BookmarksController(BookmarkContext dbContext)
        {
            _dbContext = dbContext;
        }

        [HttpPost]
        public IActionResult Post([FromBody]Bookmark newBookmark)
        {

            _dbContext.Bookmarks.Add(newBookmark);
            _dbContext.SaveChanges();

            return Created(string.Format("/api/Bookmark/{0}", newBookmark.ID), newBookmark);
        }

        [HttpDelete("{id}")]
        public IActionResult Delete(int id)
        {
            _dbContext.Bookmarks.Remove(_dbContext.Bookmarks.Where(tx => tx.ID == id).SingleOrDefault());
            _dbContext.SaveChanges();

            return NoContent();
        }
    }
}

Note: For the purposes of this post, we aren’t going to worry about exception handling.

Refactoring

We have two endpoints, one for creating a new bookmark, and one for deleting. And you’ll notice the Entity Framework code does just that – adds and removes. That maps nicely to Add(Bookmark) and Remove(id) repository methods, so lets first create an interface to program to instead.

1
2
3
4
5
6
7
8
namespace MyApp.DataAccess
{
    public interface IBookmarkRepository
    {
        void Add(Bookmark Bookmark);
        void Remove(int id); 
    }
}

And for the concrete version…

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
namespace MyApp.DataAccess
{
    public class BookmarkRepository : IBookmarkRepository
    {
        BookmarkContext _dbContext;

        public BookmarkRepository(BookmarkContext dbContext)
        {
            _dbContext = dbContext;
        }

        public void Add(Bookmark Bookmark)
        {
            _dbContext.Bookmarks.Add(Bookmark);
            _dbContext.SaveChanges();
        }

        public void Remove(int id)
        {
            _dbContext.Bookmarks.Remove(_dbContext.Bookmarks.Where(tx => tx.ID == id).SingleOrDefault());
            _dbContext.SaveChanges();
        }

    }
}

Note: these will live in our data access layer…

Fortunately, and with great pleasure, aspnetcore will handle injecting the DbContext into the constructor just like it did when we used it in the controller.

Psssst! Know anyone who might want to read this?

Injecting the repository interface

To use our new repository we have to tell the dependency injection framework that when it needs to fulfill an IBookmarkRepository reference, that it should use a specific concrete implementation – the one we created above.

To do this, add this to your ConfigureServices() method in Startup.cs:

services.AddScoped<IBookmarkRepository, BookmarkRepository>();

If you were to implement the interface using another ORM like Dapper, a mock repository for testing, or with no ORM at all, this is where you would specify which implementation to use.

Piece of cake!

Now we just need to make use of it in our original controller method:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
namespace MyApp.WebApi.Controllers
{
    [Route("api/[controller]")]
    public class BookmarksController : Controller
    {

        private readonly IBookmarkRepository _BookmarkRepo;

        public BookmarksController(IBookmarkRepository repo)
        {
            _BookmarkRepo = repo;
        }

        [HttpPost]
        public IActionResult Post([FromBody]Bookmark Bookmark)
        {
            var newBookmark = _mapper.Map<Bookmark>(Bookmark);

            _BookmarkRepo.Add(newBookmark);

            return Created(string.Format("/api/Bookmark/{0}", newBookmark.ID), newBookmark);
        }

        [HttpDelete("{id}")]
        public IActionResult Delete(int id)
        {
            _BookmarkRepo.Remove(id);

            return NoContent();
        }
    }
}

And that’s all she wrote.

Still lots to do, but this is a great start.

Continue getting regular doses of .NET clarity by joining my newsletter.