Introduction
Modern enterprise applications require more than just simple CRUD operations. As applications grow larger, developers must focus on writing clean, maintainable, scalable, and organised code. This is where backend architecture patterns such as Repository Pattern and Unit of Work become extremely important.
Additionally, most real-world applications need stored procedures, server-side pagination, sorting, and searching, especially when handling large sets of data. When combined, these concepts help you build a stable, high-performance, production-quality application.
In this guide, you will learn how to create a complete backend architecture using:
Repository Pattern
Unit of Work Pattern
Stored Procedures in SQL Server
EF Core as the ORM
ASP.NET Core Web API as the backend
Pagination, Sorting, and Searching
Angular frontend (for consuming the API)
This is a complete and professional architecture used in real enterprise-level projects.
Let us begin.
Understanding Repository Pattern
What is the Repository Pattern?
Repository Pattern ensures that all database operations pass through a dedicated class called a repository.
Instead of writing database code inside controllers, you keep all data access logic in repositories.
This helps in:
Clean separation between business logic and data access
Easy unit testing
Cleaner controllers
Reusable data logic
Better maintainability
A simple example of repository responsibilities:
Get all products
Add a product
Update a product
Delete a product
Get product by ID
Call stored procedures
The controller should only call repository methods and should not know how the data is fetched.
Understanding Unit of Work (UoW)
What is the Unit of Work Pattern?
Unit of Work Pattern ensures that when multiple repositories perform operations, the database save process is coordinated so that all changes either commit or fail together.
In short:
UoW manages multiple repositories
Ensures a single save point
Helps avoid inconsistent data
Makes transactions easier
In large applications, Unit of Work becomes extremely important.
Creating the Database and Stored Procedures
Let us create a Products table.
CREATE TABLE Products
(
Id INT PRIMARY KEY IDENTITY(1,1),
Name NVARCHAR(100),
Price DECIMAL(18,2),
Quantity INT
);
Stored Procedure for Pagination, Sorting, Searching
CREATE PROCEDURE GetProductsPaged
@PageNumber INT,
@PageSize INT,
@SearchTerm NVARCHAR(100) = NULL,
@SortColumn NVARCHAR(50) = 'Name',
@SortDirection NVARCHAR(4) = 'ASC'
AS
BEGIN
SET NOCOUNT ON;
DECLARE @Offset INT = (@PageNumber - 1) * @PageSize;
SELECT *
FROM Products
WHERE (@SearchTerm IS NULL
OR Name LIKE '%' + @SearchTerm + '%')
ORDER BY
CASE WHEN @SortColumn = 'Name' AND @SortDirection = 'ASC' THEN Name END ASC,
CASE WHEN @SortColumn = 'Name' AND @SortDirection = 'DESC' THEN Name END DESC,
CASE WHEN @SortColumn = 'Price' AND @SortDirection = 'ASC' THEN Price END ASC,
CASE WHEN @SortColumn = 'Price' AND @SortDirection = 'DESC' THEN Price END DESC
OFFSET @Offset ROWS FETCH NEXT @PageSize ROWS ONLY;
END
This stored procedure supports:
Pagination
Keyword search
Sorting by multiple columns
Setting Up ASP.NET Core with EF Core
Step 1: Create Model
Models/Product.cs
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
public decimal Price { get; set; }
public int Quantity { get; set; }
}
Step 2: Create AppDbContext
using Microsoft.EntityFrameworkCore;
using System.Data;
public class AppDbContext : DbContext
{
public AppDbContext(DbContextOptions<AppDbContext> options) : base(options) {}
public DbSet<Product> Products { get; set; }
public DataTable ExecuteStoredProcedure(string procedureName, params object[] parameters)
{
using var cmd = this.Database.GetDbConnection().CreateCommand();
cmd.CommandText = procedureName;
cmd.CommandType = CommandType.StoredProcedure;
foreach (var p in parameters)
{
cmd.Parameters.Add(p);
}
this.Database.OpenConnection();
using var reader = cmd.ExecuteReader();
var dt = new DataTable();
dt.Load(reader);
return dt;
}
}
Implementing Repository Pattern
Step 3: Create Generic Repository Interface
Repositories/IGenericRepository.cs
public interface IGenericRepository<T> where T : class
{
Task<IEnumerable<T>> GetAllAsync();
Task<T> GetByIdAsync(int id);
Task AddAsync(T entity);
void Update(T entity);
void Delete(T entity);
}

Join the conversation! Your thoughts help the community grow.