Repository Pattern

Part 06 - Repository Pattern

The Repository pattern is a design pattern in software development that provides a way to encapsulate data access logic. The pattern abstracts the data access logic and data storage location from the rest of the application, allowing for a cleaner separation of concerns and increased maintainability.

The Repository pattern defines a set of methods for performing CRUD (Create, Read, Update, Delete) operations on a data source, such as a database or API. This abstraction allows the application to switch between different data sources, such as from a database to an API, without having to change the data access code in the application.

The Repository pattern is often used in conjunction with other patterns, such as the Unit of Work pattern and the Service Layer pattern, to provide a comprehensive solution for data access and management. In an MVC (Model-View-Controller) architecture, the Repository pattern is often implemented in the Model layer.

A Repository pattern is a design pattern that mediates data from Application Domain to Data Access Layers (like Entity Framework Core / Dapper). Using it, we are promoting a more loosely coupled approach to accessing our data from the database. Repositories are classes that store or retrieve data. This pattern allows you to have a cleaner separation of concerns. A repository pattern is one of the heavily used Design Patterns to build clean solutions. In addition, this pattern helps avoid API bloating.

Data access logic is in a separate class, or set of classes called a repository, with the responsibility of persisting the application’s business model. This example shows the Repository pattern implementation. You can navigate the code of the individual components by clicking on the highlighted elements.

appsettings.json: set up the database connection string in this configuration file

DPContext.cs: create a dapper context in this class

EFContext.cs: create an entity framework context in this class. Also, make the DbSet<T> domain objects map to database tables

BaseRepository.cs: this base class is designed for inheritance and creates references to dpContext, efContext, and exposes SaveDBContext() method, which will save the efContext state.

IRepository.cs: this interface is designed for implementation and exposes six generic CRUD, Filtering, and Pagination methods.

UserRepository: create an IUserRepository and implement CRUD, Filter, and Paging logic to [User] database table.

ErrorRepository: create an IErrorRepository and implement CRUD, Filter, and Paging logic to [Error] database table. A dapper method, InsertRecordDapper(), is also implemented here.

StartUp.cs: inject EFContext, DPContext, and Repositories (UserRepository and ErrorRepository)

BaseRepository
using BPX.DAL.Context;

namespace BPX.DAL.Repositories
{
    public abstract class BaseRepository
    {
        protected readonly EFContext efContext;

        public BaseRepository(EFContext efContext)
        {
            this.efContext = efContext;
        }

        public void SaveDBChanges()
        {
            efContext.SaveChanges();
        }
    }
}

Notes:
notes.

EateryRepository
using BPX.DAL.Context;
using BPX.Domain.DbModels;
using Microsoft.EntityFrameworkCore;
using System.Linq.Expressions;

namespace BPX.DAL.Repositories
{
    public class EateryRepository : BaseRepository, IEateryRepository
    {
        public EateryRepository(EFContext efContext) : base(efContext)
        {
        }

        public Eatery? GetRecordById(int id)
        {
            return efContext.Eaterys.Where(c => c.EateryId.Equals(id)).SingleOrDefault();
        }

        public IQueryable<Eatery> GetRecordsByFilter(Expression<Func<Eatery, bool>> filter)
        {
            return efContext.Eaterys.Where(filter);
        }

        public void InsertRecord(Eatery entity)
        {
            efContext.Eaterys.Add(entity);
        }

        public void UpdateRecord(Eatery entity)
        {
            efContext.Entry(entity).State = EntityState.Modified;
        }
    }

    public interface IEateryRepository : IRepository<Eatery>
    {
    }
}

Notes:
notes.

IRepository
using System.Linq.Expressions;

namespace BPX.DAL.Repositories
{
    public interface IRepository<T> where T : class
    {
        T? GetRecordById(int id);

        // TODO :: GetPaginatedRecords();

        IQueryable<T> GetRecordsByFilter(Expression<Func<T, bool>> filter);

        void InsertRecord(T entity);

        void UpdateRecord(T entity);
    }
}

Notes:
none.

RoleRepository
using BPX.DAL.Context;
using BPX.Domain.DbModels;
using Microsoft.EntityFrameworkCore;
using System.Linq.Expressions;

namespace BPX.DAL.Repositories
{
    public class RoleRepository : BaseRepository, IRoleRepository
    {
        public RoleRepository(EFContext efContext) : base(efContext)
        {
        }

        public Role? GetRecordById(int id)
        {
            return efContext.Roles.Where(c => c.RoleId.Equals(id)).SingleOrDefault();
        }

        public IQueryable<Role> GetRecordsByFilter(Expression<Func<Role, bool>> filter)
        {
            return efContext.Roles.Where(filter);
        }

        public void InsertRecord(Role entity)
        {
            efContext.Roles.Add(entity);
        }

        public void UpdateRecord(Role entity)
        {
            efContext.Entry(entity).State = EntityState.Modified;
        }
    }

    public interface IRoleRepository : IRepository<Role>
    {
    }
}

Notes:
none.

UserRepository
using BPX.DAL.Context;
using BPX.Domain.DbModels;
using Microsoft.EntityFrameworkCore;
using System.Linq.Expressions;

namespace BPX.DAL.Repositories
{
    public class UserRepository : BaseRepository, IUserRepository
    {
        public UserRepository(EFContext efContext) : base(efContext)
        {
        }

        public User? GetRecordById(int id)
        {
            return efContext.Users.Where(c => c.UserId.Equals(id)).SingleOrDefault();
        }

        public IQueryable<User> GetRecordsByFilter(Expression<Func<User, bool>> filter)
        {
            return efContext.Users.Where(filter);
        }

        public void InsertRecord(User entity)
        {
            efContext.Users.Add(entity);
        }

        public void UpdateRecord(User entity)
        {
            efContext.Entry(entity).State = EntityState.Modified;
        }
    }

    public interface IUserRepository : IRepository<User>
    {
    }
}

Notes:
none.

UserRoleRepository
using BPX.DAL.Context;
using BPX.Domain.DbModels;
using Microsoft.EntityFrameworkCore;
using System.Linq.Expressions;

namespace BPX.DAL.Repositories
{
    public class UserRoleRepository : BaseRepository, IUserRoleRepository
    {
        public UserRoleRepository(EFContext efContext) : base(efContext)
        {
        }

        public UserRole? GetRecordById(int id)
        {
            throw new NotImplementedException();
        }

        public IQueryable<UserRole> GetRecordsByFilter(Expression<Func<UserRole, bool>> filter)
        {
            return efContext.UserRoles.Where(filter);
        }

        public void InsertRecord(UserRole entity)
        {
            efContext.UserRoles.Add(entity);
        }

        public void UpdateRecord(UserRole entity)
        {
            efContext.Entry(entity).State = EntityState.Modified;
        }
    }

    public interface IUserRoleRepository : IRepository<UserRole>
    {
    }
}

Notes:
none.

TestController
using BPX.DAL.Repositories;
using Microsoft.AspNetCore.Mvc;

namespace BPX.Website.Controllers
{
    public class TestController : BaseController<TestController>
    {
        public RoleRepository roleRepository;
        public UserRepository userRepository;
        public UserRoleRepository userRoleRepository;
        public EateryRepository eateryRepository;

        public TestController(ILogger<TestController> logger, IRoleRepository roleRepository, IUserRepository userRepository, IUserRoleRepository userRoleRepository, IEateryRepository eateryRepository) : base(logger)
        {
            this.roleRepository = (RoleRepository) roleRepository;
            this.userRepository = (UserRepository)userRepository;
            this.userRoleRepository = (UserRoleRepository)userRoleRepository;
            this.eateryRepository = (EateryRepository)eateryRepository;
        }

        public IActionResult Index()
        {
            var rslt1 = roleRepository.GetRecordById(3);
            var rslt2 = userRepository.GetRecordsByFilter(c => c.StatusFlag.Equals("A")).ToList();
            var rslt3 = userRoleRepository.GetRecordsByFilter(c => c.UserId.Equals(102)).ToList();

            return View();
        }
    }
}

Notes:
none

Program.cs
using BPX.DAL.Context;
using BPX.DAL.Repositories;
using Microsoft.EntityFrameworkCore;
using NLog;
using NLog.Web;

var logger = NLog.LogManager.Setup().LoadConfigurationFromAppSettings().GetCurrentClassLogger();
logger.Debug("init main");

try
{
    var builder = WebApplication.CreateBuilder(args);

    var connectionString = builder.Configuration.GetConnectionString("BPXConnectionString");
    builder.Services.AddDbContext<EFContext>(x => x.UseSqlServer(connectionString));

    // inject repositories
    builder.Services.AddScoped<IRoleRepository, RoleRepository>();
    builder.Services.AddScoped<IUserRepository, UserRepository>();
    builder.Services.AddScoped<IUserRoleRepository, UserRoleRepository>();
	builder.Services.AddScoped<IEateryRepository, EateryRepository>();

    // Add services to the container.
    builder.Services.AddControllersWithViews();

    // NLog: Setup NLog for Dependency injection
    builder.Logging.ClearProviders();
    builder.Host.UseNLog();

    var app = builder.Build();

    // Configure the HTTP request pipeline.
    app.UseExceptionHandler("/Exception/Error");
    app.UseStatusCodePagesWithReExecute("/Exception/Index", "?statusCode={0}");

    app.UseStaticFiles();

    app.UseRouting();

    app.UseAuthorization();

    app.MapControllerRoute(
        name: "default",
        pattern: "{controller=Home}/{action=Index}/{id?}");

    app.Run();
}
catch (Exception exception)
{
    // NLog: catch setup errors
    logger.Error(exception, "Stopped program because of exception");
    throw;
}
finally
{
    // Ensure to flush and stop internal timers/threads before application-exit (Avoid segmentation fault on Linux)
    NLog.LogManager.Shutdown();
}

Notes:
none



 



Ginger CMS
the future of cms, a simple and intuitive content management system ...

ASP.NET MVC Application
best practices like Repository, LINQ, Dapper, Domain objects ...

CFTurbine
cf prototyping engine, generates boilerplate code and views ...

Search Engine LITE
create your own custom search engine for your web site ...

JRun monitor
monitors the memory footprint of JRun engine and auto-restarts a hung engine ...

Validation Library
complete validation library for your web forms ...