🚀 Step 2: Implementing a Generic Repository (Introducing Category Repository)
💡 Goal:
In this article, we will refactor our Product Repository by implementing a Generic Repository Pattern. Additionally, we will introduce the Category Repository, demonstrating how the Generic Repository improves code reusability and maintainability.
📂 Updated Project Structure (With Generic Repository)
📂 ProductApp
├── 📂 Core # Domain Layer
│ ├── 📂 Entities
│ │ ├── BaseEntity.cs
│ │ ├── Product.cs
│ │ ├── Category.cs
│ ├── 📂 Interfaces
│ │ ├── IRepository.cs # Generic Repository Interface
│ │ ├── IProductRepository.cs
│ │ ├── ICategoryRepository.cs
│
├── 📂 Infrastructure # Data Access Layer
│ ├── 📂 Data
│ │ ├── Repository.cs # Generic Repository Implementation
│ │ ├── ProductRepository.cs
│ │ ├── CategoryRepository.cs
│ │ ├── StoreContext.cs
│
├── 📂 API # Presentation Layer
│ ├── 📂 Controllers
│ │ ├── ProductsController.cs
│ │ ├── CategoriesController.cs
│ ├── 📂 DTOs
│ │ ├── ProductDto.cs
│ │ ├── CategoryDto.cs
1️⃣ Define the Base Entity
📂 Core/Entities/BaseEntity.cs
namespace Core.Entities
{
public abstract class BaseEntity
{
public int Id { get; set; }
}
}
2️⃣ Define the Generic Repository Interface
📂 Core/Interfaces/IRepository.cs
using System.Collections.Generic;
using System.Threading.Tasks;
namespace Core.Interfaces
{
public interface IRepository<T> where T : BaseEntity
{
Task<T?> GetByIdAsync(int id);
Task<List<T>> ListAllAsync();
void Add(T entity);
void Update(T entity);
void Remove(T entity);
Task<bool> SaveAllAsync();
}
}
3️⃣ Implement the Generic Repository
📂 Infrastructure/Data/Repository.cs
using System.Collections.Generic;
using System.Threading.Tasks;
using Core.Entities;
using Core.Interfaces;
using Microsoft.EntityFrameworkCore;
namespace Infrastructure.Data
{
public class Repository<T> : IRepository<T> where T : BaseEntity
{
private readonly StoreContext _context;
private readonly DbSet<T> _dbSet;
public Repository(StoreContext context)
{
_context = context;
_dbSet = _context.Set<T>();
}
public async Task<T?> GetByIdAsync(int id)
{
return await _dbSet.FindAsync(id);
}
public async Task<List<T>> ListAllAsync()
{
return await _dbSet.ToListAsync();
}
public void Add(T entity)
{
_dbSet.Add(entity);
}
public void Update(T entity)
{
_dbSet.Update(entity);
}
public void Remove(T entity)
{
_dbSet.Remove(entity);
}
public async Task<bool> SaveAllAsync()
{
return await _context.SaveChangesAsync() > 0;
}
}
}
4️⃣ Define the **Category
**** Entity**
📂 Core/Entities/Category.cs
namespace Core.Entities
{
public class Category : BaseEntity
{
public string Name { get; set; } = string.Empty;
}
}
5️⃣ Implement the **ICategoryRepository
**** Interface**
📂 Core/Interfaces/ICategoryRepository.cs
using Core.Entities;
namespace Core.Interfaces
{
public interface ICategoryRepository : IRepository<Category>
{
}
}
6️⃣ Implement the **CategoryRepository
**
📂 Infrastructure/Data/CategoryRepository.cs
using Core.Entities;
using Core.Interfaces;
namespace Infrastructure.Data
{
public class CategoryRepository : Repository<Category>, ICategoryRepository
{
public CategoryRepository(StoreContext context) : base(context) { }
}
}
7️⃣ Register the Repositories in **Program.cs
**
📂 API/Program.cs
using Infrastructure.Data;
using Microsoft.EntityFrameworkCore;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
// Register StoreContext with Dependency Injection
builder.Services.AddDbContext<StoreContext>(options =>
options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection")));
// Register Repositories
builder.Services.AddScoped(typeof(IRepository<>), typeof(Repository<>));
builder.Services.AddScoped<IProductRepository, ProductRepository>();
builder.Services.AddScoped<ICategoryRepository, CategoryRepository>();
var app = builder.Build();
// Apply Migrations Automatically (Optional: Good for development)
using var scope = app.Services.CreateScope();
var services = scope.ServiceProvider;
var context = services.GetRequiredService<StoreContext>();
context.Database.Migrate();
app.UseSwagger();
app.UseSwaggerUI();
app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
app.Run();
8️⃣ Create and Apply Migrations
Run the following commands in the API project folder:
# Create a Migration
dPM> dotnet ef migrations add AddCategoryEntity
No project was found. Change the current working directory or use the --project option.
# Apply the Migration to Update the Database
dotnet ef database update
🚀 Step 2 is Complete
✔ We refactored the Product Repository using a Generic Repository.\
✔ We introduced the Category Repository, demonstrating reusability.\
✔ The Generic Repository now enables easy expansion for future entities.\
✔ We added and applied migrations to update the database.
🔜 What’s Next?
In the next article, we will introduce the Specification Pattern to handle complex queries efficiently. This will allow filtering, sorting, and pagination without cluttering our repositories.
Stay tuned! 🚀