Monday, November 17, 2025

Architecture - Repository & Generic Repository Patterns

 ✅ 1. Repository Pattern

What it is

A Repository acts as an abstraction layer between your domain/business logic and the data access layer.
Instead of writing EF Core queries directly in controllers/services, you wrap them inside repository classes.

Why we use it

  • Keeps business logic independent from persistence logic

  • Easy to mock for unit testing

  • Central place to maintain DB queries


Example: Product Repository (Non-Generic)

Entity

public class Product { public int Id { get; set; } public string Name { get; set; } public decimal Price { get; set; } }

IProductRepository

public interface IProductRepository { Task<Product> GetByIdAsync(int id); Task<IEnumerable<Product>> GetAllAsync(); Task AddAsync(Product product); void Update(Product product); void Delete(Product product); }

Implementation

public class ProductRepository : IProductRepository { private readonly AppDbContext _context; public ProductRepository(AppDbContext context) { _context = context; } public async Task<Product> GetByIdAsync(int id) => await _context.Products.FindAsync(id); public async Task<IEnumerable<Product>> GetAllAsync() => await _context.Products.ToListAsync(); public async Task AddAsync(Product product) => await _context.Products.AddAsync(product); public void Update(Product product) => _context.Products.Update(product); public void Delete(Product product) => _context.Products.Remove(product); }

👉 This is a repository dedicated only to Product.


2. Generic Repository Pattern

What it is

A Generic Repository provides a reusable base class for CRUD operations.
Instead of writing a repository per entity, you write one that works with all entities.

Why we use it

  • Avoids duplicated CRUD code

  • Cleaner structure

  • Extensible for entity-specific repositories


IGenericRepository

public interface IGenericRepository<T> where T : class { Task<T> GetByIdAsync(int id); Task<IEnumerable<T>> GetAllAsync(); Task AddAsync(T entity); void Update(T entity); void Delete(T entity); }

Implementation

public class GenericRepository<T> : IGenericRepository<T> where T : class { protected readonly AppDbContext _context; public GenericRepository(AppDbContext context) { _context = context; } public async Task<T> GetByIdAsync(int id) => await _context.Set<T>().FindAsync(id); public async Task<IEnumerable<T>> GetAllAsync>() => await _context.Set<T>().ToListAsync(); public async Task AddAsync(T entity) => await _context.Set<T>().AddAsync(entity); public void Update(T entity) => _context.Set<T>().Update(entity); public void Delete(T entity) => _context.Set<T>().Remove(entity); }

🔥 Entity-Specific Repository + Generic Repository

Sometimes we extend generic repository for custom queries:

public interface IProductRepository : IGenericRepository<Product> { Task<IEnumerable<Product>> GetProductsAbovePrice(decimal price); }
public class ProductRepository : GenericRepository<Product>, IProductRepository { public ProductRepository(AppDbContext context) : base(context) { } public async Task<IEnumerable<Product>> GetProductsAbovePrice(decimal price) { return await _context.Products .Where(p => p.Price > price) .ToListAsync(); } }

No comments:

Post a Comment

CI/CD - Safe DB Changes/Migrations

Safe DB Migrations means updating your database schema without breaking the running application and without downtime . In real systems (A...