Separation Of Concern And Data Access From Within Code

It is difficult to talk about application architecture without talking about the separation of concern (SoC).

Separation of concern (SoC) is a software development concept that separates an application or a software into different sections, or concerns, where each concern has a different purpose.

Example

The example below illustrates the concept of the separation of concern and each project in the solution explorer represents a concern or a section that can encapsulate information that can be developed and updated independently.

  • WebApp.Model - represents the data layer.
  • WebApp.Repository - represents the bridge of communication between the data layer and business logic layer.
  • WebApp.Template - represents business logic layer.

layer

In this article, I will talk about WebApp.Repository section and how it should be implemented to apply abstraction layer between the data access layer and the business logic layer.

Before starting the code and the explanation of each part let’s see a brief definition of a repository design pattern according to Exam Ref 70-486 book.

The primary data access pattern in C# is the Repository pattern, which is intended to create an abstraction layer between the data access layer and the business logic layer. This abstraction helps you handle any changes in either the business logic or the data access layer by breaking the dependencies between the two. It also enables the business logic layer to access the repository without knowing the specific type of data it is accessing, such as a Mi­crosoft SharePoint list or a database. What the repository does internally is separate from the business logic layer. 

Let’s jump into it and do more practice.

The first step is to create the necessary interfaces:  

  1. public interface IRepository < TEntity > where TEntity: class 
  2. {  
  3.     
  4.     TEntity GetById(int id);  
  5.     IEnumerable < TEntity > GetAll();
  6.   
  7.     void Add(TEntity tEntity);  
  8.     void AddRange(IEnumerable < TEntity > tEntity);
  9.   
  10.     void Remove(TEntity tEntity);  
  11.     void AddRemove(IEnumerable < TEntity > tEntity);  
  12. }  

IRepositoy interface is a generic interface where it passes any type of type “class” or i passes any type that is a reference type because the keyword “class” represents a reference type.

If you want to pass a value type you should replace “class” keyword with “struct”.

In this case, I will keep class keyword because I am going to deal with objects.

Also, notice that there are three sets of signature methods in IRepository interface.

Signature methods for retrieving, adding and removing objects.

  1. public interface IProductRepository: IRepository < Product > 
  2. {  
  3.     IEnumerable < Product > GetProductByManufactoryName(string manufactoryName);  
  4. }  

IProductRepository interface implements the generic IRepository of type Product.

Also, IProductRepository interface will have the signatures of all your custom methods that treat anything related to a Product data.

Product - represents an entity.

If you have a different Entity Order then you should create a new interface IOrderRepository.

For example, public interface IOrderRepository: IRepository<Order> should have its own signatures methods that treat anything related to an Order data.

  1. public interface IUnitOfWork: IDisposable 
  2. {  
  3.     IProductRepository ProductRepository 
  4.     {  
  5.         get;  
  6.     }

  7.     // IOrderRepository OrderRepository {get;}  
  8.     int Save();  
  9. }  

Unit of Work design pattern allows the coordination of multiple repositories by creating a single shared class for them all.

IUnitOfWork interface implements an IDisposable interface that contains Dispose method Also, I added “Save ()” that will do the persistence.

Second step is the implementation of the interfaces and get all the benefits: 

  1. public class Repository < TEntity > : IRepository < TEntity > where TEntity: class 
  2. {  
  3.     protected readonly DbContext _dbContext;
  4.  
  5.     public Repository(DbContext dbContext) 
  6.     {  
  7.         _dbContext = dbContext;  
  8.     }  
  9.     public void Add(TEntity tEntity) 
  10.     {  
  11.         _dbContext.Set < TEntity > ().Add(tEntity);  
  12.     }  
  13.     public void AddRange(IEnumerable < TEntity > tEntity) 
  14.     {  
  15.         _dbContext.Set < TEntity > ().AddRange(tEntity);  
  16.     }  
  17.     public IEnumerable < TEntity > GetAll() 
  18.     {  
  19.         return dbContext.Set < TEntity > ().ToList();  
  20.     }  
  21.     public TEntity GetById(int id) 
  22.     {  
  23.         return _dbContext.Set < TEntity > ().Find(id);  
  24.     }  
  25.     public void Remove(TEntity tEntity) 
  26.     {  
  27.         _dbContext.Set < TEntity > ().Remove(tEntity);  
  28.     }  
  29.     public void RemoveRange(IEnumerable < TEntity > tEntity) 
  30.     {  
  31.         _dbContext.Set < TEntity > ().RemoveRange(tEntity);  
  32.     }  
  33. }  

The generic class IRepository implements the generic IRepository interface to use all its defined methods.

 DbContext is injected in Repository constructor.

_dbContext is marked as protected because it should be used in ProductRepository class. Also, notice that the DbContext class is a part of the Entity Framework and it comes from using System.Data.Entity.

  1. public class ProductRepository: Repository < Product > , IProductRepository 
  2. {  
  3.     public ProductRepository(NORTHWINDEntities dbcontext): base(dbcontext) {}  
  4.     public NORTHWINDEntities GetData {  
  5.         get 
  6.         {  
  7.             return _dbContext as NORTHWINDEntities;  
  8.         }  
  9.     }  
  10.     public IEnumerable < Product > GetProductByManufactoryName(string manufactoryName) 
  11.     {  
  12.         return GetData.Products.ToList();  
  13.     }  
  14. }  

ProductRepository class implements IProductRepository to get the customized method

GetProductByManufactoryName

Notice that NORTHWINDEntities class comes from your Model (WebApp.Model concern) and it inherits from DbContext. 

Here is the piece of code where your NORTHWINDEntities lives,

  1. public partial class NORTHWINDEntities: DbContext 
  2. {  
  3.     public NORTHWINDEntities(): base("name= NORTHWINDEntities ") {}  
  4.     public virtual DbSet < Product > Products 
  5.     {  
  6.         get;  
  7.         set;  
  8.     }  
  9. }  

UnitOfWork that contributes the work of all repositories,

  1. public class UnitOfWork: IUnitOfWork 
  2. {  
  3.     private readonly NORTHWINDEntities _dbContext;
  4.   
  5.     public UnitOfWork(NORTHWINDEntities dbContext) 
  6.     {  
  7.         _dbContext = dbContext;  
  8.         ProductRepository = new ProductRepository(_dbContext);  
  9.     }  
  10.     public IProductRepository ProductRepository 
  11.     {  
  12.         get;  
  13.     }  
  14.     public int Save() 
  15.     {  
  16.         return _dbContext.SaveChanges();  
  17.     }  
  18.     public void Dispose() 
  19.     {  
  20.         _dbContext.Dispose();  
  21.     }  
  22. }  

Finally, use of UnitOfWork to call your repository.

  1. using WebApp.Model;  
  2. using WebApp.Repository.core;  
  3. using WebApp.Repository.repository;  
  4. Public class HomeController: Controller 
  5. {  
  6.     private readonly IUnitOfWork _unitOfWork;
  7.   
  8.     Public HomeController() 
  9.     {  
  10.         _unitOfWork = new UnitOfWork(new NORTHWNDEntities());  
  11.     }  
  12.     public async Task < ActionResult > Index() 
  13.     {  
  14.         // Search Product By ManufactoryName  
  15.         Product product = _unitOfWork.Product.GetProductByManufactoryName("Ayoubation");  
  16.         if (product != null
  17.         {  
  18.             ViewBag.myProduct = product.ProductName;  
  19.         }

  20.         // Update product  
  21.         product.ProductName = "Mercedes";
  22.   
  23.         // Add new Product  
  24.         Product myProduct = new Product 
  25.         {  
  26.                 ProductName = "Celine disc",  
  27.                 Discontinued = true  
  28.         };
  29.   
  30.         _unitOfWork.Product.Add(myProduct);  
  31.         _unitOfWork.Save();
  32.   
  33.         // Search All  
  34.         IEnumerable < Product > products = _unitOfWork.Product.GetAll();  
  35.         _unitOfWork.Dispose();  
  36.         return View(products);  
  37.     }  
  38. }  

That’s it. You have implemented the abstraction layer between the business logic and data access layer by applying the Soc.

G
M
T
 
Text-to-speech function is limited to 200 characters