Entity Framework  

How to Implement Repository and Unit of Work with Entity Framework

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:

  • Organize data access logic

  • Improve code readability

  • Make your application easier to test

  • Follow clean architecture principles

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:

  • You create a repository

  • All database operations go through that repository

Why use Repository Pattern?

  • Centralized data access logic

  • Easier to maintain and update

  • Better unit testing support

  • Clean separation of concerns

Real-life example

Think of a repository like a library manager:

  • You don’t go directly to the storage room

  • You ask the manager to fetch or store books

What is Unit of Work Pattern?

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

In simple words

  • It keeps track of all changes

  • Saves everything together

  • Ensures data consistency

Why use Unit of Work?

  • Prevent partial updates

  • Manage transactions efficiently

  • Improve performance

Real-life example

Think of it like a shopping checkout system:

  • You add items to cart

  • Payment happens once

  • Everything is processed together

Why Use Repository + Unit of Work Together?

When combined:

  • Repository handles data operations

  • Unit of Work manages transactions

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

  • Repository handles CRUD operations

  • Unit of Work saves all changes together

  • Controller uses Unit of Work instead of DbContext

This creates a clean and maintainable architecture.

Benefits of Repository and Unit of Work Pattern

  • Clean architecture

  • Better separation of concerns

  • Easier testing (mock repositories)

  • Reusable data access logic

  • Improved maintainability

Common Mistakes to Avoid

  • Overusing generic repository unnecessarily

  • Mixing business logic inside repository

  • Not using async methods

  • Ignoring transaction handling

Best Practices

  • Use specific repositories when needed

  • Keep repositories focused on data access only

  • Use Unit of Work for transaction control

  • Follow SOLID principles

Real-World Use Case

In an e-commerce application:

  • Order creation

  • Payment processing

  • Inventory update

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.