Skip to main content

🔄 Generic Repository Pattern in .NET with Example

The Generic Repository Pattern helps us centralize common data access logic (CRUD) and keeps our code clean and maintainable. Let’s walk through building a generic repository and using it for a simple Product entity.


📝 Step 1: Define the Generic Repository Interface

// IGenericRepository.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Threading.Tasks;

public interface IGenericRepository<T> where T : class
{
    Task<T> GetByIdAsync(object id);
    Task<IEnumerable<T>> GetAllAsync();
    Task<IEnumerable<T>> FindAsync(Expression<Func<T, bool>> predicate);

    Task AddAsync(T entity);
    void Update(T entity);
    void Remove(T entity);

    IQueryable<T> Query();
}

🛠️ Step 2: Implement the Generic Repository

// GenericRepository.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;

public class GenericRepository<T> : IGenericRepository<T> where T : class
{
    protected readonly DbContext _context;
    protected readonly DbSet<T> _dbSet;

    public GenericRepository(DbContext context)
    {
        _context = context ?? throw new ArgumentNullException(nameof(context));
        _dbSet = _context.Set<T>();
    }

    public virtual async Task<T> GetByIdAsync(object id)
    {
        return await _dbSet.FindAsync(id);
    }

    public virtual async Task<IEnumerable<T>> GetAllAsync()
    {
        return await _dbSet.ToListAsync();
    }

    public virtual async Task<IEnumerable<T>> FindAsync(Expression<Func<T, bool>> predicate)
    {
        return await _dbSet.Where(predicate).ToListAsync();
    }

    public virtual async Task AddAsync(T entity)
    {
        await _dbSet.AddAsync(entity);
    }

    public virtual void Update(T entity)
    {
        if (_context.Entry(entity).State == EntityState.Detached)
            _dbSet.Attach(entity);

        _context.Entry(entity).State = EntityState.Modified;
    }

    public virtual void Remove(T entity)
    {
        _dbSet.Remove(entity);
    }

    public virtual IQueryable<T> Query()
    {
        return _dbSet.AsQueryable();
    }
}

📦 Step 3: Create the Product Entity

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

🗂️ Step 4: Setup the DbContext

using Microsoft.EntityFrameworkCore;

public class AppDbContext : DbContext
{
    public AppDbContext(DbContextOptions<AppDbContext> options) : base(options) { }

    public DbSet<Product> Products { get; set; }
}

⚙️ Step 5: Register Repository in Dependency Injection

Add this in Program.cs (or Startup.cs in older projects):

builder.Services.AddDbContext<AppDbContext>(options =>
    options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection")));

builder.Services.AddScoped(typeof(IGenericRepository<>), typeof(GenericRepository<>));

🛒 Step 6: Use Repository in a Service Layer

public class ProductService
{
    private readonly IGenericRepository<Product> _productRepo;

    public ProductService(IGenericRepository<Product> productRepo)
    {
        _productRepo = productRepo;
    }

    public async Task AddProductAsync(string name, decimal price)
    {
        var product = new Product { Name = name, Price = price };
        await _productRepo.AddAsync(product);
    }

    public async Task<IEnumerable<Product>> GetAllProductsAsync()
    {
        return await _productRepo.GetAllAsync();
    }

    public async Task<Product> GetProductByIdAsync(int id)
    {
        return await _productRepo.GetByIdAsync(id);
    }

    public void UpdateProduct(Product product)
    {
        _productRepo.Update(product);
    }

    public void DeleteProduct(Product product)
    {
        _productRepo.Remove(product);
    }
}

🌐 Step 7: Expose API Endpoints with a Controller

[ApiController]
[Route("api/[controller]")]
public class ProductsController : ControllerBase
{
    private readonly ProductService _productService;
    private readonly AppDbContext _context;

    public ProductsController(ProductService productService, AppDbContext context)
    {
        _productService = productService;
        _context = context;
    }

    [HttpPost]
    public async Task<IActionResult> Create(string name, decimal price)
    {
        await _productService.AddProductAsync(name, price);
        await _context.SaveChangesAsync(); // Persist changes
        return Ok("Product added successfully!");
    }

    [HttpGet]
    public async Task<IActionResult> GetAll()
    {
        var products = await _productService.GetAllProductsAsync();
        return Ok(products);
    }
}

⚡ Key Takeaways

  • Repository handles data access (CRUD, queries).
  • Service layer contains business logic (validations, workflows).
  • ✅ Always call SaveChangesAsync() on DbContext (or use a Unit of Work to centralize commits).

👉 Up next, we can enhance this with a Unit of Work pattern, so the controller doesn’t need to manually call SaveChangesAsync().

Comments

Popular posts from this blog

🏗️ Deep Dive: Understanding Every Concept in Microsoft Entra API Onboarding for .NET Developers

When working with Microsoft Entra (formerly Azure Active Directory), you’ll hear terms like App Registration, Tenant, Client ID, Audience, Scopes, Roles, Tokens, OBO flow , and more. If you’re new, it can feel overwhelming. This guide breaks down every key term and concept , with definitions, examples, and how they connect when you onboard and consume a new API. 🔹 1. Tenant Definition : A tenant in Entra ID is your organization’s dedicated, isolated instance of Microsoft Entra. Think of it like : Your company’s identity directory. Example : contoso.onmicrosoft.com is a tenant for Contoso Ltd. 🔹 2. App Registration Definition : The process of registering an application in Entra to give it an identity and permission to use Microsoft identity platform. Why needed : Without registration, Entra doesn’t know about your app. What it creates : Application (Client) ID – unique identifier for your app Directory (Tenant) ID – your organization’s ID Types of apps : Web ...

🗑️ Garbage Collection & Resource Management in .NET (C#) — Beginner Friendly Guide

When you start working with .NET and C#, one of the biggest advantages is that you don’t need to manually manage memory like in C or C++. The Garbage Collector (GC) does most of the work for you. But here’s the catch — not everything is managed automatically. Some resources like files, database connections, sockets, and native memory still need special handling. This blog will help you understand: ✔ How the GC works ✔ What are managed vs unmanaged resources ✔ The difference between Dispose , Finalize , and using ✔ The Dispose pattern with examples ✔ Best practices every C# developer should know 1) How Garbage Collection Works in .NET Managed resources → Normal .NET objects (string, List, etc.). GC frees them automatically. Unmanaged resources → External resources like file handles, database connections, sockets, native memory. GC cannot clean them up — you must do it. 👉 GC uses a Generational Model for performance: Gen 0 : Short-lived objects (local variables, t...

☁️ Azure Key vault Short Notes

🟢 What is Azure Key Vault? A cloud service for securely storing and accessing secrets, keys, and certificates . Removes the need to keep secrets (like connection strings, passwords, API keys) inside code or config files. Provides centralized secret management, encryption, and access control . 👉 Think of it like a secure password manager but for your applications. 🟢 Key Features Secrets → store text values (e.g., DB connection string, API key). Keys → store cryptographic keys (RSA, EC) for encryption, signing. Certificates → store/manage SSL/TLS certificates. Access Control → Access Policies (older model). Azure RBAC (modern, preferred). Integration → works with App Service, Functions, AKS, VMs, SQL DB, etc. Logging → audit who accessed secrets via Azure Monitor / Diagnostic Logs. 🟢 Why Use Key Vault? Security → secrets are encrypted with HSM (Hardware Security Modules). Compliance → meet industry standards (PCI-DSS, ISO, GDPR). Automation → aut...