In the last module we learned how to use the DI pattern. We used dependency injection to inject an instance of the FredsCarsDbContext
service into the controller.
private FredsCarsDbContext _context;
public HomeController(FredsCarsDbContext context)
{
_context = context;
}
This is better than manually instantiating the DbContext with the C# new keyword as shown in the following.
public HomeController()
{
DbContextOptionsBuilder<FredsCarsDbContext> opts =
new DbContextOptionsBuilder<FredsCarsDbContext>(
// build options
);
var context = new FredsCarsDbContext(opts.Options);
}
But, we can still benefit from separating out the DbContext and data access by one more layer, further loosely coupling the DbContext service from the Controller component.
The Repository Pattern
We can do this with another popular pattern called called the repository pattern.
This pattern is widely used and can help us to reuse code and reduce code duplication. It can also provide a consistent way to access data through the DbContext. This is especially true with CRUD methods.
NOTE: CRUD stands for Create, Read, Update, and Delete, which are the four basic operations for managing data in a database.
Create the Vehicle Repository
Create the Vehicle Repository Interface
The first thing we need to do is create an Interface for the Vehicle Repository. Create a new folder called Repositories under the Models folder. Then create an interface called IVehicleRepository
by right clicking on the new Repositories folder and selecting Add -> Class
.

In the Add New Item dialogue, select Interface from the middle pane, name the file IVehicleRepository.cs, and click the Add button.

Modify IVehicleRepository.cs with the code shown below.
FredsCars\Models\Repositories\IVehicleRepository.cs
namespace FredsCars.Models.Repositories
{
public interface IVehicleRepository
{
IQueryable<Vehicle> Vehicles { get; }
}
}
In the code for the Interface above, we have declared a read only property called Vehicles that returns an IQueryable<T>
, in our case an IQueryable<Vehicle>
. The IQueryable<Vehicle>
allows us to fetch a set of Vehicles in an abstract way.
Create the Vehicle Repository Implementation
The next thing we need to do is create an implementation of IVehicleInterface
. Create a class in the Models\Repositories folder called EFVehicleRepository
and modify it with the code below.
FredsCars\Models\Repositories\EFVehicleRepository.cs
using FredsCars.Data;
namespace FredsCars.Models.Repositories
{
public class EFVehicleRepository : IVehicleRepository
{
private FredsCarsDbContext _context;
public EFVehicleRepository(FredsCarsDbContext context)
{
_context = context;
}
public IQueryable<Vehicle> Vehicles => _context.Vehicles;
}
}
In the code above, the first thing we do is perform DI and inject an instance of FredsCarsDbContext into the repository service.
private FredsCarsDbContext _context;
public EFVehicleRepository(FredsCarsDbContext context)
{
_context = context;
}
Next, the EFVehicleRepository
is implementing the IVehicleRepository
interface. When you implement an interface, you have to implement every public property and method of the interface. Right now the IVehicleRepository
interface only has one member, the Vehicles property. So we implement that property by returning the DbSet of Vehicles.
public IQueryable<Vehicle> Vehicles => _context.Vehicles;
In the code line above, we are not returning actual data records from the database but rather a DbSet
. DbSet
inherits from IQueryable
. And IQueryable
inherits from IEnumerable
.
The IQueryable<T>
interface inherits from IEnumerable<T>
but it provides additional capabilities for querying data while still supporting basic enumeration.
We can build up queries using an IQueryable<T>
in a method without hitting the database using LINQ methods such as Where
and OrderBy
.
public IQueryable<Vehicle> Vehicles
=> _context.Vehicles
.Where(v => v.VehicleType.Name == "Car")
.OrderBy(v => v.Year);
Execution of IQueryable
LINQ methods like Where
and OrderBy
are deferred. The SQL commands are not sent to the database until we either iterate through the DbSet with a foreach loop or call ToList() or ToListAsync() on the DbSet.
using FredsCars.Data;
namespace FredsCars.Models.Repositories
{
public class EFVehicleRepository : IVehicleRepository
{
private FredsCarsDbContext _context;
public EFVehicleRepository(FredsCarsDbContext context)
{
_context = context;
}
public IQueryable<Vehicle> Vehicles => _context.Vehicles;
public List<Vehicle> SomeMethod()
{
List<Vehicle> results = new List<Vehicle>();
// iteration gets data from database
foreach (var vehicle in Vehicles)
{
if (vehicle.VehicleType.Name == "Car")
{
results.Add(vehicle);
}
}
return results;
}
}
}
public List<Vehicle> SomeMethod()
{
// ToList() calls database and fetches data
return _context.Vehicles.ToList();
}
Also note, the Vehicles property in the implementation is used to map the Vehicles property in the interface to the Vehicles DbSet
property in the DbContext
.
Register the Vehicle Repository as a service
The final thing we need to do is register the Vehicle Repository, or more accurately the IVehicle interface, as a service. Modify Program.cs with the code below.
FredsCars/Program.cs
using FredsCars.Data;
using FredsCars.Models;
using FredsCars.Models.Repositories;
using Microsoft.EntityFrameworkCore;
var builder = WebApplication.CreateBuilder(args);
// Add Services
builder.Services.AddControllersWithViews();
builder.Services.AddDbContext<FredsCarsDbContext>(opts =>
{
opts.UseSqlServer(
builder.Configuration["ConnectionStrings:FredsCarsMvcConnection"]
);
});
builder.Services.AddScoped<IVehicleRepository, EFVehicleRepository>();
var app = builder.Build();
// Configure the HTTP request pipeline.
... existing code ...
Earlier in the chapter we talked about service lifetimes. In the code above we use the AddScoped
method to add the IVehicleRepository
service with a lifetime of scoped. This means a new IVehicleRepository
instance will be created for each new request. And that same instance will be used as each component in the request lifetime does it’s job; from the middleware components chain on through to the controller.
builder.Services.AddScoped<IVehicleRepository, EFVehicleRepository>();
The AddScoped
method takes two generic types; TService
and TImplimentation
.
AddScoped<TService, TImplementation>();
The TService
type we are registering as the service is IVehicleRepository
. The TImplimentation
we are registering as the implementation class of the interface is EFVehicleRepository
.
By setting up and registering a repository in this way, any component in the application that depends on the IVehicleRepository
service doesn’t know or care about what class implements the service. It also doesn’t know the details of the implementation. This makes it easy to swap out another implementation class in the service registration in Program.cs without having to make any changes in the components that use the service, for instance, the controller.
Also notice we imported the FredsCars.Models.Repositories
namespace into Program.cs with the using statement.
using FredsCars.Models.Repositories;
Inject the Repository into the Controller
Now that we have the repository ready to go, the last thing we need to do is inject it into the controller, replacing the FredsCarsDbContext
service with the IVehicleRespository
service. Modify the Home Controller with the modified code below.
FredsCars\Controllers\HomeController.cs
using FredsCars.Data;
using FredsCars.Models.Repositories;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
namespace FredsCars.Controllers
{
public class HomeController : Controller
{
private IVehicleRepository _repo;
public HomeController(IVehicleRepository repo)
{
_repo = repo;
}
public async Task<ViewResult> Index()
{
return View(await _repo.Vehicles
.Include(v => v.VehicleType)
.ToListAsync());
}
}
}
In the code above, we still use dependency injection for the controller. But now we inject the vehicle repository containing the DbContext into the controller rather then the DbContext itself. To do this we change the type of the private field from FredsCarsDbContext
to IVehicleRepository
and the name from _context to _repo.
private IVehicleRepository _repo;
In the constructor we also change the type of the incoming constructor from FredsCarsDbContext
to IVehicleRepository
and rename it from context to repo. Then we assign the incoming repo service to the private field.
public HomeController(IVehicleRepository repo)
{
_repo = repo;
}
Also, we no longer need the FredsCars.Data
namespace but we did need to import the FredsCars.Models.Repositories
namespace. This is because we are no longer accessing the FredsCarsDbContext
class directly but rather through the repository.
If you restart and run the application the results should remain the same. But, now with the Vehicle Repository in place it is very easy to swap out different implementations for the IVehicleRepository
service, or in other words, how the controller accesses and works with the database.
For instance, say we want to access the database using the ORM (Object Relational Mapper) called NHibernate rather than EF Core. We could just register a new implementation class for the IVehicleRepository
service in Program.cs called NHVehicleRepository to replace EFVehcileRepository.
builder.Services.AddScoped<IVehicleRepository, NHVehicleRepository>();
Another benefit of being able to easily swap service implementations is during testing.
The repository layer loosely couples and isolates the controller from data access. If the controller was tightly coupled with the data access component, and we got an error during testing, we wouldn’t know if the error lied with the data access or in the controller itself.
In the next module we are going to create a Unit Test for the controller. In the Unit Test we will be able to create an implementation for the IVehicleRepository
service explicitly for testing. To do this we will create a fake repository, or mock the IVehicleRepository
with an implementation that returns exactly the data we specify instead of real data from the database. This way we know what data to expect and can test for that result.
So, during run time the Home controller will use the EFVehicleRepository
implementation. And during testing it will use the mock implementation.
What’s Next
In this chapter we continued to build on the DI pattern and learned the Repository pattern while we created a repo for Vehicles.
In the next chapter we will create our first unit test for the home controller to make sure it can properly use the repository.