Generic Repository Nedir ve Neden Kullanılır?
Generic Repository, farklı entity’ler için ortak CRUD işlemlerini tek bir sınıfta soyutlamanızı sağlar. Bu sayede:
- Kod Tekrarı Azalır: Her entity için ayrı repository yazmak yerine tek bir generic sınıf kullanırsınız.
- Bakım Kolaylığı: Yeni entity eklediğinizde repository tarafında ekstra kod yazmanıza gerek kalmaz.
- Tutarlılık: Tüm CRUD operasyonları aynı implamentasyondan beslenir, davranış farkları minimize edilir.
Tasarım Prensipleri
- Single Responsibility (SRP): Generic repository sadece veri erişim metotlarını barındırır.
- Open/Closed (OCP): Yeni entity türü eklemek için repository sınıfı değiştirilmez, sadece T tipi değişir.
- Dependency Inversion (DIP): Servis katmanı, concrete sınıfa değil
IRepository<T>arayüzüne bağımlı olur.
Pratik Not: Eğer bazı entity’leriniz çok özel sorgulara ihtiyaç duyuyorsa, generic repository’yi base olarak kullanıp o entity’ye özgü metotlar içeren “specialized” repository’ler türetebilirsiniz.
ASP.NET Core ile Örnek Uygulama
1. IRepository<T> Arayüzü
public interface IRepository<T> where T : class
{
Task<T> GetByIdAsync(int id);
Task<IEnumerable<T>> GetAllAsync();
IQueryable<T> Query();
Task AddAsync(T entity);
void Update(T entity);
void Delete(T entity);
}
Query()metodu, LINQ filtreleri servis katmanına bırakmak içinIQueryable<T>döner.
2. EfRepository<T> Sınıfı
public class EfRepository<T> : IRepository<T> where T : class
{
protected readonly AppDbContext _context;
public EfRepository(AppDbContext context) => _context = context;
public async Task<T> GetByIdAsync(int id) =>
await _context.Set<T>().FindAsync(id);
public async Task<IEnumerable<T>> GetAllAsync() =>
await _context.Set<T>().ToListAsync();
public IQueryable<T> Query() =>
_context.Set<T>().AsNoTracking();
public async Task AddAsync(T entity) =>
await _context.Set<T>().AddAsync(entity);
public void Update(T entity) =>
_context.Set<T>().Update(entity);
public void Delete(T entity) =>
_context.Set<T>().Remove(entity);
}
Örnek Kullanım:
var products = await _repository.Query()
.Where(p => p.IsActive)
.OrderBy(p => p.Name)
.ToListAsync();
Gerçek Projelerde Dikkat Edilmesi Gerekenler
- AsNoTracking: Sadece okunacak verilerde
AsNoTracking()kullanarak performansı artırın. - Sorgu Filtreleme: Özel filtreler veya eager loading gereken durumlarda
Query()üzerinden LINQ kullanın. - Transaction Yönetimi: Birden fazla entity güncellemesi varsa
UnitOfWorkile beraber kullanın; tek bir commit’te tüm değişiklikleri uygulayın. - Specialized Repository: Generic yapının yeterli olmadığı karmaşık sorgular için
IProductRepository : IRepository<Product>gibi arayüzler oluşturup, sadece o sorgulara özgü metotlar ekleyin.
Servis Katmanına Entegrasyon
public class ProductService
{
private readonly IRepository<Product> _repository;
private readonly IUnitOfWork _unitOfWork;
public ProductService(IRepository<Product> repository, IUnitOfWork unitOfWork)
{
_repository = repository;
_unitOfWork = unitOfWork;
}
public async Task<IEnumerable<Product>> GetActiveProductsAsync()
{
return await _repository.Query()
.Where(p => p.IsActive)
.ToListAsync();
}
public async Task AddProductAsync(Product product)
{
await _repository.AddAsync(product);
await _unitOfWork.CommitAsync();
}
}
İleri Okuma ve Kaynaklar
Sonuç
Generic Repository, projedeki temel CRUD işlemlerini tek bir yerde toplamak için güçlü bir yöntemdir. Ancak:
- Çok farklı sorgulara ihtiyaç duyulan entity’lerde specialized repository’ler kullanarak esnekliği koruyun.
Query()metoduyla LINQ izinleri verin, ancakAsNoTracking()ve eager loading detaylarına dikkat edin.
Doğru dengede kullandığınızda, kod tekrarını azaltır, bakımı kolaylaştırır ve test edilebilirliği artırırsınız.