In large enterprise applications, your domain isn’t just a few entities — it’s hundreds: Vendors, Products, Orders, Invoices, Customers, etc.
If you:
- Create one repository per entity
- Create one service per entity
- Create one controller per entity
👉 You’ll drown in boilerplate code and your solution will become unmanageable!
🚀 The Problem
Imagine:
IVendorRepository → VendorRepository → VendorService → VendorController
IProductRepository → ProductRepository → ProductService → ProductController
IOrderRepository → OrderRepository → OrderService → OrderController
...
➡ Result? Hundreds of repetitive files, copy-paste logic everywhere.
💡 The Solution: Go Generic!
✅ 1️⃣ Generic Repository
Define a base repository for all entities:
public interface IRepository<TEntity> where TEntity : class
{
Task<TEntity> GetByIdAsync(Guid id);
Task<IEnumerable<TEntity>> GetAllAsync();
Task AddAsync(TEntity entity);
Task UpdateAsync(TEntity entity);
Task DeleteAsync(Guid id);
Task SaveChangesAsync();
}
public class Repository<TEntity> : IRepository<TEntity> where TEntity : class
{
private readonly AppDbContext _context;
private readonly DbSet<TEntity> _dbSet;
public Repository(AppDbContext context)
{
_context = context;
_dbSet = context.Set<TEntity>();
}
public async Task AddAsync(TEntity entity)
{
await _dbSet.AddAsync(entity);
}
public async Task DeleteAsync(Guid id)
{
var entity = await _dbSet.FindAsync(id);
if (entity == null) throw new KeyNotFoundException();
_dbSet.Remove(entity);
}
public async Task<IEnumerable<TEntity>> GetAllAsync()
=> await _dbSet.ToListAsync();
public async Task<TEntity> GetByIdAsync(Guid id)
{
var entity = await _dbSet.FindAsync(id);
if (entity == null) throw new KeyNotFoundException();
return entity;
}
public async Task UpdateAsync(TEntity entity)
{
_dbSet.Update(entity);
}
public async Task SaveChangesAsync()
=> await _context.SaveChangesAsync();
}
👉 One repository, reused everywhere:
IRepository<Vendor>
IRepository<Product>
IRepository<Order>
✅ 2️⃣ Generic Service
public interface IService<TEntity> where TEntity : class
{
Task<TEntity> GetByIdAsync(Guid id);
Task<IEnumerable<TEntity>> GetAllAsync();
Task AddAsync(TEntity entity);
Task UpdateAsync(TEntity entity);
Task DeleteAsync(Guid id);
}
//Service Class
public class Service<TEntity> : IService<TEntity> where TEntity : class
{
private readonly IRepository<TEntity> _repository;
public Service(IRepository<TEntity> repository)
{
_repository = repository;
}
public async Task<TEntity> GetByIdAsync(Guid id)
=> await _repository.GetByIdAsync(id);
public async Task<IEnumerable<TEntity>> GetAllAsync()
=> await _repository.GetAllAsync();
public async Task AddAsync(TEntity entity)
{
await _repository.AddAsync(entity);
await _repository.SaveChangesAsync();
}
public async Task UpdateAsync(TEntity entity)
{
await _repository.UpdateAsync(entity);
await _repository.SaveChangesAsync();
}
public async Task DeleteAsync(Guid id)
{
await _repository.DeleteAsync(id);
await _repository.SaveChangesAsync();
}
}
✅ 3️⃣ Generic Controller
[ApiController]
[Route("api/[controller]")]
public class GenericController<TEntity> : ControllerBase where TEntity : class
{
private readonly IService<TEntity> _service;
public GenericController(IService<TEntity> service)
{
_service = service;
}
[HttpGet("{id}")]
public async Task<IActionResult> GetById(Guid id)
=> Ok(await _service.GetByIdAsync(id));
[HttpGet]
public async Task<IActionResult> GetAll()
=> Ok(await _service.GetAllAsync());
[HttpPost]
public async Task<IActionResult> Create([FromBody] TEntity entity)
{
await _service.AddAsync(entity);
return Ok(entity);
}
[HttpPut]
public async Task<IActionResult> Update([FromBody] TEntity entity)
{
await _service.UpdateAsync(entity);
return Ok();
}
[HttpDelete("{id}")]
public async Task<IActionResult> Delete(Guid id)
{
await _service.DeleteAsync(id);
return Ok();
}
}
👉 Custom logic? Just override:
// Customer Controller Also
public class VendorController : GenericController<Vendor>
{
public VendorController(IService<Vendor> service) : base(service) { }
[HttpGet("special")]
public IActionResult GetSpecial()
{
return Ok("Special Vendor Data");
}
}
✅ DI Registration
builder.Services.AddDbContext<AppDbContext>(…);
builder.Services.AddScoped(typeof(IRepository<>), typeof(Repository<>));
builder.Services.AddScoped(typeof(IService<>), typeof(Service<>));
builder.Services.AddDbContext<AppDbContext>(…);
builder.Services.AddScoped(typeof(IRepository<>), typeof(Repository<>));
builder.Services.AddScoped(typeof(IService<>), typeof(Service<>));
⚡ Benefits
- 📉 Less boilerplate — One set of logic, reused
- 🛠 Easier maintenance — Changes in one place apply everywhere
- 🚀 Scales cleanly — Add entities without code explosion
- 🧩 Customizable — Override for special cases
🌟 Final Thought
When your domain grows, your architecture should scale with it. By introducing generic patterns, you reduce redundancy, improve clarity, and set yourself up for long-term success.


















