Repository pattern in asp.net core razor pages

Suggested Videos
Part 28 - Install entity framework core in class library project | Text | Slides
Part 29 - Using sql server in razor pages project | Text | Slides
Part 30 - EF core migrations in razor pages project | Text | Slides

In this video, we will discuss 
  • What is Repository Pattern and it's benefits.
  • An example that uses repository pattern in asp.net core razor pages project

What is Repository Pattern

Repository Pattern is an abstraction of the Data Access Layer. It hides the details of how data is saved or retrieved from the underlying data source. The details of how 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. Yet, another repository that stores and retrieves data from an XML or CSV file.


public class Employee
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string Email { get; set; }
    public string PhotoPath { get; set; }
    public Dept? Department { get; set; }
}

Repository Pattern Interface
  1. The interface in the repository pattern specifies
  2. What operations (i.e methods) are supported by the repository
  3. The data required for each of the operations i.e the parameters that need to be passed to the method and the data the method returns
  4. The repository interface contains what it can do, but not, how it does.
  5. The implementation details are in the respective repository class that implements the repository Interface
public interface IEmployeeRepository
{
    IEnumerable<Employee> GetAllEmployees();
    Employee GetEmployee(int id);
    Employee Add(Employee newEmployee);
    Employee Update(Employee updatedEmployee);
    Employee Delete(int id);
    IEnumerable<DeptHeadCount> EmployeeCountByDept(Dept? dept);
    IEnumerable<Employee> Search(string searchTerm);
}

IEmployeeRepository interface supports the following operations
  1. Get all the employees
  2. Get a single employee by id
  3. Add a new employee
  4. Updat an employee
  5. Delete an employee
  6. Get total count of employees by department
  7. Search and return a filtered list of employees
The details of how these operations are implemented are in the repository class that implements this IEmployeeRepository interface 

The following MockEmployeeRepository class provides an implementation for IEmployeeRepository. This specific implementation stores and retrieves employees from an in-memory collection.

public class MockEmployeeRepository : IEmployeeRepository
{
    private List<Employee> _employeeList;

    public MockEmployeeRepository()
    {
        _employeeList = new List<Employee>()
        {
            new Employee() { Id = 1, Name = "Mary", Department = Dept.HR,
                Email = "mary@pragimtech.com", PhotoPath="mary.png" },
            new Employee() { Id = 2, Name = "John", Department = Dept.IT,
                Email = "john@pragimtech.com", PhotoPath="john.png" },
            new Employee() { Id = 3, Name = "Sara", Department = Dept.IT,
                Email = "sara@pragimtech.com", PhotoPath="sara.png" },
            new Employee() { Id = 4, Name = "David", Department = Dept.Payroll,
                Email = "david@pragimtech.com" },
        };
    }

    public Employee Add(Employee newEmployee)
    {
        newEmployee.Id = _employeeList.Max(e => e.Id) + 1;
        _employeeList.Add(newEmployee);
        return newEmployee;
    }

    public Employee Delete(int id)
    {
        Employee employeeToDelete =
            _employeeList.FirstOrDefault(e => e.Id == id);

        if (employeeToDelete != null)
        {
            _employeeList.Remove(employeeToDelete);
        }

        return employeeToDelete;
    }

    public IEnumerable<DeptHeadCount> EmployeeCountByDept(Dept? dept)
    {
        IEnumerable<Employee> query = _employeeList;
        if(dept.HasValue)
        {
            query = query.Where(e => e.Department == dept.Value);
        }
        return query.GroupBy(e => e.Department)
                            .Select(g => new DeptHeadCount()
                            {
                                Department = g.Key.Value,
                                Count = g.Count()
                            }).ToList();
    }

    public IEnumerable<Employee> GetAllEmployees()
    {
        return _employeeList;
    }

    public Employee GetEmployee(int id)
    {
        return _employeeList.FirstOrDefault(e => e.Id == id);
    }

    public IEnumerable<Employee> Search(string searchTerm)
    {
        if(string.IsNullOrEmpty(searchTerm))
        {
            return _employeeList;
        }

        return _employeeList.Where(e => e.Name.Contains(searchTerm) ||
                                        e.Email.Contains(searchTerm));
    }

    public Employee Update(Employee updatedEmployee)
    {
        Employee employee = _employeeList
            .FirstOrDefault(e => e.Id == updatedEmployee.Id);

        if (employee != null)
        {
            employee.Name = updatedEmployee.Name;
            employee.Email = updatedEmployee.Email;
            employee.Department = updatedEmployee.Department;
            employee.PhotoPath = updatedEmployee.PhotoPath;
        }

        return employee;
    }
}

Repository Pattern - SQL Server Implementation

The following SQLEmployeeRepository class provides another implementation for IEmployeeRepository. This specific implementation stores and retrieves employees from a sql server database using entity framework core.

public class SQLEmployeeRepository : IEmployeeRepository
{
    private readonly AppDbContext context;

    public SQLEmployeeRepository(AppDbContext context)
    {
        this.context = context;
    }
       
    public Employee Add(Employee newEmployee)
    {
        context.Employees.Add(newEmployee);
        context.SaveChanges();
        return newEmployee;
    }

    public Employee Delete(int id)
    {
        Employee employee = context.Employees.Find(id);
        if(employee != null)
        {
            context.Employees.Remove(employee);
            context.SaveChanges();
        }
        return employee;
    }

    public IEnumerable<DeptHeadCount> EmployeeCountByDept(Dept? dept)
    {
        IEnumerable<Employee> query = context.Employees;
        if (dept.HasValue)
        {
            query = query.Where(e => e.Department == dept.Value);
        }
        return query.GroupBy(e => e.Department)
                            .Select(g => new DeptHeadCount()
                            {
                                Department = g.Key.Value,
                                Count = g.Count()
                            }).ToList();
    }

    public IEnumerable<Employee> GetAllEmployees()
    {
        return context.Employees;
    }

    public Employee GetEmployee(int id)
    {
        return context.Employees
                .FromSqlRaw<Employee>("spGetEmployeeById {0}", id)
                .ToList()
                .FirstOrDefault();
    }

    public IEnumerable<Employee> Search(string searchTerm)
    {
        if (string.IsNullOrEmpty(searchTerm))
        {
            return context.Employees;
        }

        return context.Employees.Where(e => e.Name.Contains(searchTerm) ||
                                        e.Email.Contains(searchTerm));
    }

    public Employee Update(Employee updatedEmployee)
    {
        var employee = context.Employees.Attach(updatedEmployee);
        employee.State = Microsoft.EntityFrameworkCore.EntityState.Modified;
        context.SaveChanges();
        return updatedEmployee;
    }
}

Which implementation to use

Take a look at the following Index razor page. ASP.NET Core Dependency injection system injects an instance of IEmployeeRepository.

public class IndexModel : PageModel
{
    private readonly IEmployeeRepository employeeRepository;

    public IEnumerable<Employee> Employees { get; set; }
        
    [BindProperty(SupportsGet = true)]
    public string SearchTerm { get; set; }

    public IndexModel(IEmployeeRepository employeeRepository)
    {
        this.employeeRepository = employeeRepository;
    }

    public void OnGet()
    {
        Employees = employeeRepository.Search(SearchTerm);
    }
}

There are 2 implementations for IEmployeeRepository interface. 
  1. MockEmployeeRepository
  2. SQLEmployeeRepository
How does the application know which implementation to use. The answer to this is in Startup class in Startup.cs file. With the following line of code, ASP.NET Core provides an instance of SQLEmployeeRepository class when an instance of IEmployeeRepository is requested. 

public void ConfigureServices(IServiceCollection services)
{
    // Rest of the code
    services.AddScoped<IEmployeeRepository, SQLEmployeeRepository>();
}

We are using AddScoped() method because we want the instance to be alive and available for the entire scope of the given HTTP request. For another new HTTP request, a new instance of SQLEmployeeRepository class will be provided and it will be available throughout the entire scope of that HTTP request.

We discussed the difference between AddSingleton(), AddScoped() and AddTransient() methods in detail in Part 44 of ASP.NET Core Tutorial.

Throughout our entire application, in all the places where IEmployeeRepository is injected an instance of SQLEmployeeRepository is provided. If you want your application to use a different implementation instead, all you need to change is the following one line of code.

services.AddScoped<IEmployeeRepository, MockEmployeeRepository>();

Benefits of Repository Pattern
  • The code is cleaner, and easier to reuse and maintain.
  • Enables us to create loosely coupled systems. For example, if we want our application to work with oracle instead of sql server, implement an OracleRepository that knows how to read and write to Oracle database and register OracleRepository with the dependency injection system.
  • In an unit testing project, it is easy to replace a real repository with a fake implementation for testing.
asp.net core tutorial for beginners

1 comment:

  1. This is a well written tutorial! I'm trying to learn ASPNET Core with Razor pages and this is a clear example of how to make your code easier to maintain. Any other good resources for design patterns that are often used in dotnet development? Cheers!

    ReplyDelete

It would be great if you can help share these free resources