Learn Repository Design Pattern With .Net Core

Introduction

 
Repository is something where we can store some data/info. From an application standpoint, it's nothing but a database. We have database providers like SQL or Oracle or MongoDb etc. When we say repository, it's nothing but a usual class that encapsulates data access layer operations. Inside this class we can define logics to do CRUD operation and we can even change our database provider based on our need. In short, business logic layer and data access layer can work independently and become loosely coupled layers. We are going to use Repository design pattern to meet loose coupling of business and data access layer.
 
Before I start, let's take a look at the usual project structure. 
Here UI Layer refers to any client project like Angular / React / .net core Razor views.
 
BLL ( Business logic layer) refers to controllers or application specific BLL class.
 
DAL (Data access layer) refers to class/ component that manages application database operations. We can call it as repository.
 
Prerequisite 
  • I am going to use .Net core (with razor view) and SQL database provider. Please choose a .Net core project of your choice.
  • I will use code first approach. Hence please install Microsoft.EntityFrameworkCore from NuGet .

What are we trying to achieve?

  • Loose coupling between BLL and DAL.
  • An application independent of database provider.
So let's begin. I have defined a folder named "Database" where I am going to hold all database related operations. If you wish, you may create a separate class library project.
 
Let's add Order and Item model class to do database operation.
  1. public class Order  
  2.     {  
  3.         [Key]  
  4.         public int Id { getset; }  
  5.         public string UserName { getset; }  
  6.         [EmailAddress]  
  7.         public string EmailId { getset; }  
  8.         public string DeliveryAddress { getset; }  
  9.         [Required]  
  10.         public int PhoneNo { getset; }  
  11.         [Required]  
  12.         public List<Item> Items { getset; }          
  13.     }   
  1. public class Item  
  2.    {  
  3.        public int Id { getset; }  
  4.        public string Name { getset; }  
  5.        public string Description { getset; }  
  6.    }     
Since we are planning to change database providers, let's define connection strings in appsettings.json as below.
  1. "ConnectionStrings": {  
  2.    "AppDb""Data Source=(localdb)\\MSSQLLocalDB;Initial Catalog=master;Integrated Security=True;Database=SqlDb",  
  3.    "OracleDb""Data Source=(localdb)\\OracleDB;Initial Catalog=master;Integrated Security=True;Database=OracleDb"  
  4.  }   
Now let's add AppDbContext class for database operation as per entity framework standard. Here is the code.
  1. public class AppDbContext : DbContext  
  2.     {  
  3.         public DbSet<Order> Orders { getset; }  
  4.         public AppDbContext(DbContextOptions<AppDbContext> options):base(options)  
  5.         {  
  6.   
  7.         }  
  8.     }  
Now to meet loose coupling, we will have to define an interface called "IRepository" and we will expose this interface to the business logic layer. Then we will create its concrete classes as "SqlRepository", "OracleRepository" and "InMemoryRepository". As the name suggests, SqlRepository will connect to SQL data provider, OracleRepository will connect to Oracle data provider and InMemoryRepository, we will save data in memory (no database provider). Here is the code. 
  1. public interface IRepository  
  2.     {  
  3.         void AddOrder(Order order);  
  4.         List<Order> GetOrders();  
  5.         Order GetOrder(int orderId);  
  6.     }  
 Let's implement concrete classes as below. 
  1. public class InMemoryRepository : IRepository  
  2.    {  
  3.        private List<Order> orders;  
  4.        public InMemoryRepository()  
  5.        {  
  6.            //default value initialization.  
  7.            this.orders = new List<Order>() {  
  8.            new Order(){  
  9.                Id= 1,  
  10.                UserName="A",  
  11.                EmailId="[email protected]",  
  12.                DeliveryAddress="some address",  
  13.                Items=new List<Item>(){  
  14.                    new Item() {  
  15.                        Id= 1, Name="Item1",  
  16.                        Description="Item1 Desc"  
  17.                    },  
  18.                    new Item() {  
  19.                        Id= 2, Name="Item2",  
  20.                        Description="Item2 Desc"  
  21.                    }  
  22.                }  
  23.            }  
  24.            };  
  25.        }  
  26.        public void AddOrder(Order order)  
  27.        {  
  28.           orders.Add(order);  
  29.        }  
  30.   
  31.        public Order GetOrder(int orderId)  
  32.        {  
  33.            return orders.Find(o => o.Id == orderId);  
  34.        }  
  35.   
  36.        public List<Order> GetOrders()  
  37.        {  
  38.            return orders;  
  39.        }  
  40.    }  
When we point our application to InMemoryRepository, application will store order details in memory. We aren't going to use any  external database provider and hence the name InMemoryRepository.
  1. public class SqlRepository : IRepository  
  2.    {  
  3.        private AppDbContext dbContext;  
  4.        public SqlRepository(AppDbContext appDbContext)  
  5.        {  
  6.            dbContext = appDbContext;  
  7.        }  
  8.        public void AddOrder(Order order)  
  9.        {  
  10.            dbContext.Orders.Add(order); 
  11.            dbContext.SaveChanges(); 
  12.        }  
  13.   
  14.        public Order GetOrder(int orderId)  
  15.        {  
  16.            return dbContext.Orders.First(order => order.Id == orderId);  
  17.        }  
  18.   
  19.        public List<Order> GetOrders()  
  20.        {  
  21.            return dbContext.Orders.ToList();  
  22.        }  
  23.    }  
When we point our application to use SqlRepository, the application will save data in SQL database . As you can see we are using entity framework for database operation.

Similarly, we can define OracleRepository class the same as SqlRepository class.  I am skipping its implementation for now. Here is the skeleton structure of it.
  1. public class OracleRepository : IRepository  
  2.    {  
  3.        public void AddOrder(Order order)  
  4.        {  
  5.            //implementation to add order  
  6.        }  
  7.   
  8.        public Order GetOrder(int orderId)  
  9.        {  
  10.            //implementation  
  11.        }  
  12.   
  13.        public List<Order> GetOrders()  
  14.        {  
  15.            //implementation  
  16.        }  
  17.    }  
Our project folder structure looks like this so far.
 
 
Object Diagram of the repository structure,
 
 
Now the obvious question is, where have we defined which concrete implementation we should use. In .Net core, we should register IRepository interface implementation in ConfigureServices() method , inside Startup.cs. Once we host .Net core project the application will execute ConfigureServices() before serving any request. If we register implementation for IRepository as SqlRepository, all database operations will occur on SQL database. Similarly it can point to other databases as well. Here is the code for it.
  1. public void ConfigureServices(IServiceCollection services)  
  2.         {            
  3.             string connectionString = config.GetConnectionString("AppDb");             
  4.             services.AddDbContextPool<AppDbContext>(options => options.UseSqlServer(connectionString));  
  5.             services.AddScoped<IRepository, SqlRepository>();             
  6.         }  
Now let's see whether it's working as expected or not. Here instead of creating any UI for entering data, I am going to hard code some data for addition, in index action of home controller. In real-time applications, we will have UI to enter all data. Just to save time I am going to hard code it. If you wish, you can create your own UI for manual data entry as well.
 
Before trying to do any CRUD operation, first we need to inject IRepository interface in our HomeController. HomeController is the BLL for us. So here we can see BLL has reference to IRepository, but it is not aware of its concrete implementation. Whether the application points to SqlRepository /InMemoryRepository / OracleRepository as definition of IRepository, BLL logic is independent and hence we have achieved loose coupling between BLL and DAL.
 
Here is the code of HomeController.
  1. public class HomeController : Controller  
  2.    {  
  3.        private IRepository Repository;  
  4.        public HomeController(IRepository repository)  
  5.        {  
  6.            Repository = repository;  
  7.        }  
  8.        public IActionResult Index()  
  9.        {  
  10.            var order = new Order()  
  11.            {   
  12.                UserName = "Ram",  
  13.                EmailId = "[email protected]",  
  14.                DeliveryAddress = "address",  
  15.                Items = new List<Item>(){  
  16.                    new Item() {  
  17.                        Name="Book",  
  18.                        Description="book Desc"  
  19.                    },  
  20.                    new Item() {  
  21.                        Name="Pen",  
  22.                        Description="pen Desc"  
  23.                    }  
  24.                }  
  25.            };  
  26.            Repository.AddOrder(order);  
  27.            var allOrders = Repository.GetOrders();
  28.            return View();  
  29.        }  
  30.    }  
Let's run our application and see whether we are able to save data or not. Note currently the application is pointing to SqlRepository. 
 
Output 
 
 
As you can see record is getting saved.
 
If you are wondering why Id is 6, it's because I have tried saving some data before writing this article.
 
Now let's try to point our application to InMemory repository. I am not going to change any code in HomeController. All we have to change is, a few lines in ConfigureServices method of Startup.cs file. Here is the code. 
  1. public void ConfigureServices(IServiceCollection services)  
  2.        {              
  3.            services.AddScoped<IRepository, InMemoryRepository>();              
  4.        }  
By adding the above line of code, we are telling the application to use In-memory repository. Here SQL database is no longer linked with our application. 
 
Output
 
 
Here we see count 2. It's because we have defined one order inside its constructor.
 
Similarly, we can define implementation for OracleRepository too. I am not doing it at the moment as i don't have Oracle installed in my machine.
 
Advantage
  • Loose coupling between business logic layer and data access layer.
  • We can change database provider with minimal code change at any given point of time.
  • We can centralise database code in once project/ component.
  • We can write unit test for business logic without spying on DAL.
  • We can reuse database access code , if we define generic CRUD operations.

Summary

 
In this article we have learned how to set up repository pattern in .Net core application. By doing so we can change our database provider repository at any given point of time , with minimal code change. We can define all database related operations in one place and it can be reused. Using this design pattern makes BLL  independent of DAL.
 
I hope this article is useful for you. I am always open for any suggestions or any improvement. Thanks for finding some time to read this article.