IConfiguration vs IOptions NET
Synchronous and Asynchronous in .NET Core
Model Binding and Validation in ASP.NET Core
ControllerBase vs Controller in ASP.NET Core
ConfigureServices and Configure methods
IHostedService interface in .NET Core
ASP.NET Core request processing
| Entity Framework Core Model First | Unit of Work Patterns | |
📦 Repository Pattern in .NET Core |
The Repository pattern is a design pattern that creates an abstraction layer between an application's business logic and the data access logic. It decouples the application from the specific data persistence technology, such as Entity Framework (EF) Core.
In this pattern, a repository class is created for each aggregate root or entity, which encapsulates the logic required to store or retrieve data. This approach hides the implementation details of data access, allowing the application's business logic to focus on its core responsibilities without being tightly coupled to EF Core.
This example demonstrates a specific repository implementation for a Product entity within a .NET Core Web API, using the principles of Clean Architecture.
The Repository Pattern is a design pattern that provides an
abstraction layer between the data access layer and the
business logic layer.
Instead of working directly with DbContext or SQL queries,
you interact with repositories that encapsulate data access logic.
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
public decimal Price { get; set; }
}
public interface IProductRepository
{
Task<IEnumerable<Product>> GetAllAsync();
Task<Product> GetByIdAsync(int id);
Task AddAsync(Product product);
Task UpdateAsync(Product product);
Task DeleteAsync(int id);
}
using Microsoft.EntityFrameworkCore;
public class ProductRepository : IProductRepository
{
private readonly AppDbContext _context;
public ProductRepository(AppDbContext context)
{
_context = context;
}
public async Task<IEnumerable<Product>> GetAllAsync() =>
await _context.Products.ToListAsync();
public async Task<Product> GetByIdAsync(int id) =>
await _context.Products.FindAsync(id);
public async Task AddAsync(Product product)
{
_context.Products.Add(product);
await _context.SaveChangesAsync();
}
public async Task UpdateAsync(Product product)
{
_context.Products.Update(product);
await _context.SaveChangesAsync();
}
public async Task DeleteAsync(int id)
{
var product = await _context.Products.FindAsync(id);
if (product != null)
{
_context.Products.Remove(product);
await _context.SaveChangesAsync();
}
}
}
services.AddScoped<IProductRepository, ProductRepository>();
public class ProductsController : ControllerBase
{
private readonly IProductRepository _repository;
public ProductsController(IProductRepository repository)
{
_repository = repository;
}
[HttpGet]
public async Task<IEnumerable<Product>> Get() =>
await _repository.GetAllAsync();
}
The Product entity is a plain class that represents a product in the business domain.
// ProductService.Domain/Entities/Product.cs
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
public decimal Price { get; set; }
}
The IProductRepository interface defines the contract for data operations on the Product entity. This interface belongs in the domain layer, making the application layer dependent on this contract, not on EF Core itself.
// ProductService.Domain/Interfaces/IProductRepository.cs
public interface IProductRepository
{
Task<Product> GetByIdAsync(int id);
Task<IEnumerable<Product>> GetAllAsync();
Task AddAsync(Product product);
Task UpdateAsync(Product product);
Task DeleteAsync(Product product);
}
This is where the EF Core-specific logic resides. The ProductRepository class implements the IProductRepository interface, using ApplicationDbContext to perform database operations.
// ProductService.Infrastructure/Repositories/ProductRepository.cs
using ProductService.Domain.Entities;
using ProductService.Domain.Interfaces;
using ProductService.Infrastructure.Data;
using Microsoft.EntityFrameworkCore;
public class ProductRepository : IProductRepository
{
private readonly ApplicationDbContext _context;
public ProductRepository(ApplicationDbContext context)
{
_context = context;
}
public async Task<Product> GetByIdAsync(int id)
{
return await _context.Products.FindAsync(id);
}
public async Task<IEnumerable<Product>> GetAllAsync()
{
return await _context.Products.ToListAsync();
}
public async Task AddAsync(Product product)
{
await _context.Products.AddAsync(product);
await _context.SaveChangesAsync();
}
public async Task UpdateAsync(Product product)
{
_context.Entry(product).State = EntityState.Modified;
await _context.SaveChangesAsync();
}
public async Task DeleteAsync(Product product)
{
_context.Products.Remove(product);
await _context.SaveChangesAsync();
}
}
In the API's Program.cs, you register the DbContext and tell the dependency injection (DI) container to provide an instance of ProductRepository whenever IProductRepository is requested.
// ProductService.Api/Program.cs
using ProductService.Domain.Interfaces;
using ProductService.Infrastructure.Data;
using ProductService.Infrastructure.Repositories;
using Microsoft.EntityFrameworkCore;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection")));
builder.Services.AddScoped<IProductRepository, ProductRepository>();
// ... other services and middleware
The controller, which represents the business logic layer, depends only on the IProductRepository interface. It has no knowledge of EF Core, making it highly decoupled.
// ProductService.Api/Controllers/ProductsController.cs
[ApiController]
[Route("[controller]")]
public class ProductsController : ControllerBase
{
private readonly IProductRepository _repository;
public ProductsController(IProductRepository repository)
{
_repository = repository;
}
[HttpGet]
public async Task<IActionResult> GetAll()
{
var products = await _repository.GetAllAsync();
return Ok(products);
}
[HttpPost]
public async Task<IActionResult> AddProduct([FromBody] Product product)
{
await _repository.AddAsync(product);
return CreatedAtAction(nameof(GetAll), null, product);
}
}
The Repository Pattern is a powerful way to structure data access in .NET Core. While EF Core already provides repository-like behavior, using repositories makes your code more testable, maintainable, and aligned with clean architecture principles. For large projects, combine it with Unit of Work and Specification Pattern for maximum flexibility.
| Entity Framework Core Model First | Unit of Work Patterns | |