Introduction

When building scalable and maintainable ASP.NET Core applications using Entity Framework Core 10, managing data access properly becomes very important. If your code directly interacts with DbContext everywhere, it can quickly become messy, hard to test, and difficult to maintain.

This is where the Repository Pattern and Unit of Work Pattern in Entity Framework Core 10 come into play.

These design patterns help you:

In this article, we will understand these patterns in simple words and implement them step-by-step using ASP.NET Core and EF Core 10.

What is Repository Pattern?

The Repository Pattern is used to create a layer between your application and the database.

In simple words

Instead of writing database queries everywhere:

Why use Repository Pattern?

Real-life example

Think of a repository like a library manager:

What is Unit of Work Pattern?

The Unit of Work Pattern is responsible for managing multiple operations as a single transaction.

In simple words

Why use Unit of Work?

Real-life example

Think of it like a shopping checkout system:

Why Use Repository + Unit of Work Together?

When combined:

This creates a clean, scalable architecture in ASP.NET Core with EF Core 10.

Step 1: Create Entity Model

Let’s create a simple entity.

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

Step 2: Create DbContext

using Microsoft.EntityFrameworkCore;

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

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

Step 3: Create Generic Repository Interface

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);
}

Step 4: Implement Generic Repository

using Microsoft.EntityFrameworkCore;

public class GenericRepository<T> : IGenericRepository<T> where T : class
{
    private readonly AppDbContext _context;
    private readonly DbSet<T> _dbSet;

    public GenericRepository(AppDbContext context)
    {
        _context = context;
        _dbSet = context.Set<T>();
    }

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

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

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

    public void Update(T entity)
    {
        _dbSet.Update(entity);
    }

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

Step 5: Create Unit of Work Interface

public interface IUnitOfWork
{
    IGenericRepository<Product> Products { get; }
    Task<int> CompleteAsync();
}

Step 6: Implement Unit of Work

public class UnitOfWork : IUnitOfWork
{
    private readonly AppDbContext _context;

    public IGenericRepository<Product> Products { get; private set; }

    public UnitOfWork(AppDbContext context)
    {
        _context = context;
        Products = new GenericRepository<Product>(_context);
    }

    public async Task<int> CompleteAsync()
    {
        return await _context.SaveChangesAsync();
    }
}

Step 7: Register Services in Dependency Injection

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

Step 8: Use in Controller

[ApiController]
[Route("api/[controller]")]
public class ProductsController : ControllerBase
{
    private readonly IUnitOfWork _unitOfWork;

    public ProductsController(IUnitOfWork unitOfWork)
    {
        _unitOfWork = unitOfWork;
    }

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

    [HttpPost]
    public async Task<IActionResult> Create(Product product)
    {
        await _unitOfWork.Products.AddAsync(product);
        await _unitOfWork.CompleteAsync();
        return Ok(product);
    }
}

How This Works Together

This creates a clean and maintainable architecture.

Benefits of Repository and Unit of Work Pattern

Common Mistakes to Avoid

Best Practices

Real-World Use Case

In an e-commerce application:

All these should succeed or fail together → handled by Unit of Work.

Summary

The Repository and Unit of Work patterns in Entity Framework Core 10 help you build clean, scalable, and maintainable ASP.NET Core applications. The repository pattern abstracts data access logic, while the unit of work ensures all operations are committed as a single transaction. Together, they improve code structure, testing, and performance, making them essential for real-world enterprise applications.