Introduction
All software program requires a statistics base framework wherein it communicates (CRUD) to save and get better data. Many available technology and frameworks on the market allow accessing databases smoothly and efficiently. To builders using Microsoft .Net technology, Entity Framework or Dapper have received recognition. but, the concern isn't always the way to use those frameworks however the way to write code using those frameworks which are reusable, maintainable, and readable. this text will momentarily clarify the archive and unit of Work configuration instance to attend to all regular problems with records set admittance and enterprise cause exchanges. We likewise include some functional instances of a way to execute them in a regular .Net Core Project
Table of Contents
- What is Repository & Unit of Work Pattern
- Prerequisites
- Setup the project
- Implementing the Repository Pattern
- Generic Repository Interface
- Repository Classes
- Repository and DbContext Injection
- Dependency Injection
- Unit of Work Interface Design
- Unit of Work Implementation
- Schema Execution
- Wrapping the API Controller
- Project Structure
- Conclusion
What is Repository & Unit of Work Pattern?
The repository pattern is intended to create an abstraction layer between the data access layer and the business logic layer of an application. It is a data access pattern that prompts a more loosely coupled approach to data access. We create the data access logic in a separate class, or set of classes called a repository with the responsibility of persisting the application's business model.
Unit of Work is referred to as a single transaction that involves multiple operations of insert/update/delete and so on kinds. To say it in simple words, it means that for specific user action (say registration on a website), all the transactions like insert/update/delete and so on are done in one single transaction, rather than doing multiple database transactions. This means, one unit of work here involves insert/update/delete operations, all in one single transaction.
Prerequisites
- Visual Studio 2019 - Download from here
- .Net 5.0 SDK - Download from here
Setup the Project
- Open Visual Studio and select "Create a new project" and click the "Next" button.
- Add the "project name" and "solution name" also the choose the path to save the project in that location, click on "Next".
- Now choose the target framework ".Net 5.0" which we get once we install the SDK and also will get one more option to configure Open API support by default with that check box option.
Implementing Repository Pattern
Let us now jump into the code to set up a repository pattern to persist domain models. Here we need to add a new class library project OrderStore.Domain to add our Domain models and Repository Layer as below, the domain project represents the domain layer with necessary business logic and the Repository layer represents the persistence aspects. To keep things simple the domain layer contains two aggregates (Order and Product) with an entity each. The solution is set up as below
Order.cs
using System;
using System.ComponentModel.DataAnnotations;
namespace OrderStore.Domain.Models
{
public class Order
{
[Key]
public int OrderId { get; set; }
public string OrderDetails { get; set; }
public bool IsActive { get; set; }
public DateTime OrderedDate { get; set; }
}
}
Product.cs
using System.ComponentModel.DataAnnotations;
namespace OrderStore.Domain.Models
{
public class Product
{
[Key]
public int ProductId { get; set; }
public string Name { get; set; }
public float Price{ get; set; }
public bool isDisCountApplied { get; set; }
}
}
Generic Repository Interface
Generic Repository helps us to save and retrieve its persistent state from the database.
IGenericRepository.cs
using System.Collections.Generic;
using System.Threading.Tasks;
namespace OrderStore.Domain.Interfaces
{
public interface IGenericRepository<T> where T : class
{
Task<T> Get(int id);
Task<IEnumerable<T>> GetAll();
Task Add(T entity);
void Delete(T entity);
void Update(T entity);
}
}
Now the two entities represent Order and Product domain objects. Those two objects have their own collections since we add the Generic repository interfaces for each entity type to manage all the Crud operations. we can reuse the Generic Repository as below.
IOrderRepository.cs
using OrderStore.Domain.Models;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace OrderStore.Domain.Interfaces
{
public interface IOrderRepository : IGenericRepository<Order>
{
Task<IEnumerable<Order>> GetOrdersByOrderName(string orderName);
}
}
IProductRepository.cs
using OrderStore.Domain.Models;
namespace OrderStore.Domain.Interfaces
{
public interface IProductRepository: IGenericRepository<Product>
{
}
}
Repository Classes
The Repository Layer implements the interfaces defined in each root. This allows the repository implementation to be abstracted away from the domain layer. The Repository instance uses EFCore to connect to a SQL Server instance and perform database actions. To use EFCore we need to install the below Nuget packages to OrderStore.Repository (Create a new project (Class Library)).
Before we implementing the repository classes we need to implement the DbContext class to connect to the database. The DbContext implementation between the repository and the database.
ApplicationDbContext.cs
using Microsoft.EntityFrameworkCore;
using OrderStore.Domain.Models;
namespace OrderStore.Repository
{
public class ApplicationDbContext : DbContext
{
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) : base(options)
{ }
public DbSet<Order> Orders { get; set; }
public DbSet<Product> Products { get; set; }
}
}
Now we have the DbContext class to connect with the database we can create the necessary implementations of the Repository used by the entities Order and Product. The repository classed can now inherit from the generic abstract repository class and implement functionality that is specific to that entity. This is one of the best advantages of the repository patterns we can now use named queries returning specific business data
OrderRepository.cs
using OrderStore.Domain.Interfaces;
using OrderStore.Domain.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
namespace OrderStore.Repository
{
public class OrderRepository: GenericRepository<Order>, IOrderRepository
{
public OrderRepository(ApplicationDbContext context): base(context)
{
}
public async Task<IEnumerable<Order>> GetOrdersByOrderName(string orderName)
{
return await _context.Orders.Where(c=>c.OrderDetails.Contains(orderName)).ToListAsync();
}
}
}
ProductRepository.cs
using OrderStore.Domain.Interfaces;
using OrderStore.Domain.Models;
namespace OrderStore.Repository
{
class ProductRepository : GenericRepository<Product>, IProductRepository
{
public ProductRepository(ApplicationDbContext context) : base(context)
{
}
}
}
Repository and DbContext Injection
To use the ApplicationDbContext and the repositories we need to be able to inject them in the tp dependency injection container. We can do that by creating an extension method in the repository layer as below
DependencyInjection.cs
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using OrderStore.Domain.Interfaces;
namespace OrderStore.Repository
{
public static class DependencyInjection
{
public static IServiceCollection AddRepository(this IServiceCollection services)
{
services.AddTransient<IOrderRepository, OrderRepository>();
services.AddTransient<IProductRepository, ProductRepository>();
services.AddTransient<IUnitOfWork, UnitOfWork>();
services.AddDbContext<ApplicationDbContext>(opt => opt
.UseSqlServer("Server=DESKTOP-UUBJ14C\\SQLEXPRESS; Database=OrderDb;Trusted_Connection=True;"));
return services;
}
}
}
we can now add the AddRepository in the startup class of the API. This makes it much easy to add the necessary dependencies at the appropriate layer.
Startup.cs
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddRepository();
services.AddControllers();
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo { Title = "OrderStore", Version = "v1" });
});
}
Unit of Work Interface Design
The Unit of Work interface for our code has to sit in the interfaces folder as same above.
IUnitOfWork.cs
using System;
namespace OrderStore.Domain.Interfaces
{
public interface IUnitOfWork : IDisposable
{
IOrderRepository Orders { get; }
IProductRepository Products { get; }
int Complete();
}
}
Unit Of Work Implementation
The Unit of Work holds all the entities involved in a Business logic into a single transaction and either commits the transaction or rolls it back. The Complete implementation of the Unit Of Work interface is below.
UnitOfWork.cs
using OrderStore.Domain.Interfaces;
using System;
namespace OrderStore.Repository
{
public class UnitOfWork : IUnitOfWork
{
private readonly ApplicationDbContext _context;
public IOrderRepository Orders { get; }
public IProductRepository Products { get; }
public UnitOfWork(ApplicationDbContext bookStoreDbContext,
IOrderRepository booksRepository,
IProductRepository catalogueRepository)
{
this._context = bookStoreDbContext;
this.Orders = booksRepository;
this.Products = catalogueRepository;
}
public int Complete()
{
return _context.SaveChanges();
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
_context.Dispose();
}
}
}
}
Schema Execution
Let's add the migrations and update our Database. Run the below commands one after another in the package manager console, make sure you switch to OrderStore.Repository before executing the commands
add-migration 'Initial'
update-database
Wrapping the API Controller
We can now inject the unit of work into our Order and Product Controller. We need to retrieve the repository and its related business logic via the Unit of Work. This will ensure that the Order and Product are going to perform the Database operations. I have added two APIs in Order Controller to fetch the data from the database and in the same way, also added two more APIs in the Product Controller to Post and update the Data to the Database.
OrderController.cs
using Microsoft.AspNetCore.Mvc;
using OrderStore.Domain.Interfaces;
using OrderStore.Domain.Models;
using System.Threading.Tasks;
namespace OrderStore.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class OrderController : ControllerBase
{
private readonly IUnitOfWork _unitOfWork;
public OrderController(IUnitOfWork unitOfWork)
{
_unitOfWork = unitOfWork;
}
// GET: api/<Books>
[HttpGet(nameof(GetOrders))]
public async Task<IActionResult> GetOrders() => Ok(await _unitOfWork.Orders.GetAll());
[HttpGet(nameof(GetOrderByName))]
public async Task<IActionResult> GetOrderByName([FromQuery] string Genre) => Ok(await _unitOfWork.Orders.GetOrdersByOrderName(Genre));
}
}
ProductController.cs
using Microsoft.AspNetCore.Mvc;
using OrderStore.Domain.Interfaces;
using OrderStore.Domain.Models;
namespace OrderStore.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class ProductController : ControllerBase
{
private readonly IUnitOfWork _unitOfWork;
public ProductController(IUnitOfWork unitOfWork)
{
_unitOfWork = unitOfWork;
}
[HttpPost(nameof(CreateProduct))]
public IActionResult CreateProduct(Product product)
{
var result = _unitOfWork.Products.Add(product);
_unitOfWork.Complete();
if (result is not null) return Ok("Product Created");
else return BadRequest("Error in Creating the Product");
}
[HttpPut(nameof(UpdateProduct))]
public IActionResult UpdateProduct(Product product)
{
_unitOfWork.Products.Update(product);
_unitOfWork.Complete();
return Ok("Product Updated");
}
}
}
Project Structure
With everything tied up, we can now run the project and fire up swagger to submit a get request at the Orders endpoint. Let's see all our APIs under the swagger URL
GetOrders
Conclusion
The above article demonstrates the usage of Repository Pattern and Unit of work to ensure a clean design.
Thank you for reading, please let me know your questions, thoughts, or feedback in the comments section. I appreciate your feedback and encouragement.
You can view or download the source code from the GitHub link here.
keep Learning ...!