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()
onDbContext
(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
Post a Comment