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:
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:
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
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.