Introduction to the Repository Pattern
This is part 49 of ASP.NET Core a tutorial. In this video, we'll discuss what is repository pattern, benefits of the repository pattern, and an example that uses repository pattern to store and retrieve data from a SQL Server database using Entity Framework Core.
What is repository pattern? Repository pattern is an abstraction of the data access layer. It hides the details of how exactly the data is saved or retrieved from the underlying data source. The details of how the data is stored and retrieved is in the respective repository. For example, you may have a repository that stores and retrieves data from an in-memory collection. You may have another repository that stores and retrieves data from a database like SQL Server. You may have yet another repository that stores and retrieves data from an XML file.
Let's understand this repository pattern with an example.
Defining the Repository Interface
We want to be able to perform all the CRUD operations, that is create, read, update, and delete instances of this Employee class using the repository pattern. In its simplest form, the repository pattern contains at least two participants: the repository interface and the repository class that implements the repository interface. At the moment in our application, we are working with this employee type, so we named the repository interface IEmployeeRepository. If you're working with the product type, you would have named your repository IProductRepository. Similarly, if you're working with a customer type, then your repository would have been ICustomerRepository.
At the moment, our repository interface does not have all the CRUD operations. We only have methods that retrieve all employees, employee by ID, and then a method to add an employee, that is to create a new employee. We want methods to update and delete employees as well. First, let's include a method to update an employee.
This method is going to return us the updated employee, so let's set a return type to Employee. Let's name the method Update, and then we also want to pass the employee object that has the changes. So, pass a parameter of type Employee, and I'm going to name the parameter employeeChanges. And then let's also include a method to delete an employee. Again, this method is going to return us the deleted employee. The name of the method is Delete, and to be able to delete an employee we need the ID of that employee, so let's pass it as a parameter.
The Role of the Repository Interface
Now, the important point to keep in mind is the repository interface only specifies the operations that are supported by the repository, that is the methods that are supported by this repository. For example, this repository can retrieve an employee by ID and to be able to do that it needs the ID of the employee that it wants to retrieve and returns that employee. So we have set the return type of the GetEmployee method to Employee. So the point that I'm trying to make is the repository interface only contains what operations are supported by this repository. It does not contain the details of how those operations are performed. Those details are in the repository class that provides the implementation for this repository interface.
We already have one such class, and that is this MockEmployeeRepository class. This specific implementation stores and retrieves employee objects from an in-memory collection. Notice at the moment we have a red squiggly under IEmployeeRepository. That's because we have added two methods to the interface, that is Update and Delete, and at the moment we haven't provided implementation. So let's provide implementation for those two methods by clicking on this option, Implement interface.
Implementing the Mock In-Memory Repository
First, let's provide the implementation for the Delete method. We want to delete the employee from this private field, _employeeList. To find the employee within the list, let's use FirstOrDefault method. The ID of the employee within the collection must be equal to the incoming employee ID. Let's store that employee in a variable of type Employee.
If the employee is not equal to null, that means we have found the employee and we want to remove that employee from the collection. We use the Remove method for that and we want to remove the employee that we have found. And then finally, let's return the employee that we have deleted.
The implementation for the Update method is going to be somewhat similar to this code. So let's make a copy of it, paste it within the Update method, and then make the changes that are required. To get the ID of the employee that we want to update, let's use this incoming employee object. Once we have found the employee, we want to update the data that we have in this employee object with the data that we have in this incoming employeeChanges object. So employee.Name equals employeeChanges.Name, employee.Email equals employeeChanges.Email, and then finally employee.Department equals employeeChanges.Department, and then return the updated employee.
Creating the SQL Server Repository Class
So what have we done so far? We included our repository interface, IEmployeeRepository, and this interface is capable of performing all the CRUD operations against the employee type. We already have one implementation for this repository and that implementation is in this MockEmployeeRepository class. This implementation stores and retrieves employee objects from an in-memory collection.
We want to provide another implementation, and we want this implementation to store and retrieve employee objects from a SQL Server database using Entity Framework Core. So let's add another class file to the Models folder. I'm going to name it SqlEmployeeRepository. We want this class to provide the implementation for our repository interface, IEmployeeRepository. So let's make this class SqlEmployeeRepository implement our repository interface IEmployeeRepository.
We want this implementation to store and retrieve employee objects from a SQL Server database using Entity Framework Core. To be able to connect to the SQL server using Entity Framework Core, we need a DB context class. We already implemented this AppDbContext class in our previous videos in the series. Let's inject this AppDbContext class into our SqlEmployeeRepository class using the constructor. So first let's include a constructor and then inject the AppDbContext class. Let's name the parameter context and let's press Control-Period in Visual Studio to let Visual Studio generate the private field.
Implementing CRUD with Entity Framework Core
Next, we need to provide implementation for all the CRUD methods. Let's start with this Add method. We have the context object. On the context object, we have Employees property. Let's use the Add method to add this incoming employee object. To be able to save this employee object in the underlying SQL Server database Employees table, on the context object we have to call SaveChanges method. Let's finally return the added employee object.
To delete an employee, we must first find and retrieve the employee. So let's use the Find method on the Employees property of the context object. Let's store the employee that we have retrieved in a variable of type Employee. If employee is not null, that means we have found the employee that we want to delete. So let's delete the employee from the Employees property on the context object. For that we use the Remove method. Call SaveChanges on the context object to save the changes in the database and then finally return the deleted employee.
GetAllEmployees is pretty straightforward. We want to return all the employees. So let's return context.Employees.
Implementing the EF Core Update Method
Next, GetEmployee. This is also one line of code. We need to find employee by ID, so on the Employees property of the context object, let's use the Find method and find employee by ID, and let's return that employee.
The implementation of the Update method is slightly different. We take the employee object that has the changes and attach it to the Employees property of the context object. We use the Attach method for that, and whatever this Attach method returns, let's store it in a variable. Let's name the variable employee.
Notice from the IntelliSense, the Attach method returns EntityEntry of type Employee. Now we have to tell Entity Framework the entity that we have attached is modified. The way we do that is by setting the State property on the employee entity to Modified. Next, on the context object call SaveChanges method. When the SaveChanges method is called, that's when Entity Framework issues the required update SQL statement to update the data in the employees table in the underlying SQL Server database. Once the changes are saved, let's return the updated employee object.
Switching Implementations with Dependency Injection
At this point, we have our repository interface IEmployeeRepository and there are two implementations: in-memory implementation in MockEmployeeRepository class and SQL implementation in SqlEmployeeRepository class. And then if we take a look at HomeController within our application, notice we are injecting our repository interface IEmployeeRepository into the HomeController using the constructor. And then we are using this injected IEmployeeRepository interface in all the controller action methods. Notice we are using it in the Index action method right here, in the Details section here, and even in the Create action.
Now the important point to keep in mind here is notice nowhere within this controller we are specifying whether we want to use the in-memory implementation or the SQL implementation. The only thing that we have specified is we are saying we want to use this interface IEmployeeRepository. So the obvious question that comes to our mind at this point is how does the controllers within our application know whether they have to use MockEmployeeRepository or SqlEmployeeRepository instance?
Well, the answer to that is in the ConfigureServices method of our Startup class. So if we take a look at Startup.cs file, notice within the ConfigureServices method, you have registered MockEmployeeRepository class. So with this one line of code, we are basically saying if someone, like this HomeController, asks for an instance of IEmployeeRepository, then provide them an instance of MockEmployeeRepository. In our case, we want to store and retrieve employee objects from a SQL Server database, so instead of using MockEmployeeRepository we want to use SqlEmployeeRepository. So with this one line of code change, our entire application right now switches from storing and retrieving employee objects from an in-memory collection to a SQL Server database. So this is the power and flexibility the repository pattern combined with dependency injection provides us.
Choosing the Right Service Lifetime
Now with a SqlEmployeeRepository, we want to use AddScoped method to register our service. If you're wondering why I'm using AddScoped method, well, that's because we want the instance of the SqlEmployeeRepository class to be alive and available through the entire scope of a given HTTP request. When a new HTTP request arrives at our application we want another new instance of this class to be created, and we want that instance also to be alive and available throughout the entire scope of that HTTP request. That's the reason we're using AddScoped method to register our service.
In addition to AddScoped, we also have AddTransient and AddSingleton methods. We discussed the difference between these three methods in detail in part 44 of our ASP.NET Core tutorial.
Benefits of the Repository Pattern
What are the benefits of repository pattern? Well, the code is cleaner, easier to reuse, and maintain. It also enables us to create loosely coupled systems. If we take a look at our application, it is very loosely coupled with the specific implementation of the repository. At the moment our application is working with the employee objects in the SQL Server database because we have registered our SqlEmployeeRepository here.
Now, if we want to switch our application to work with employee objects with an in-memory collection, then all we need to do is register our MockEmployeeRepository here instead of the SqlEmployeeRepository. With this technique in a unit testing project, it is very easy to replace a real database repository implementation with a fake in-memory implementation for testing purpose.
Running the Application and Next Steps
With all these changes in place, let's run our application and see what happens. Notice we have an error: 'Cannot open database EmployeeDB'. And this error makes sense because we don't have this database created yet. We'll discuss how to create this database in our next video using Entity Framework Core migrations.
That's it in this video. Thank you for listening.