Generic Repository Nedir ve Neden Kullanılır?
Generic Repository, benzer veri erişim operasyonlarını ortak bir sınıfta toplar. EF Core’da DbSet zaten birçok generic CRUD davranışını sağlar; ayrıca wrapper yazmanın bakım ve test ihtiyacını gerçekten karşılayıp karşılamadığını değerlendirin.
- 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ığı: Ortak sözleşmeyi karşılayan yeni entity için ortak CRUD kodu tekrar yazılmaz; özel query, key veya invariant ihtiyacı ayrı kod gerektirebilir.
- Tutarlılık: Tüm CRUD operasyonları aynı implementasyondan 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.
Entity’ye özgü sorgu gerekiyorsa specialized repository veya ayrı query service seçebilirsiniz. Ortak davranış için kalıtım kullanmak zorunlu değildir; query amacını açık bir sözleşmeyle ifade etmek daha önemli olabilir.
ASP.NET Core ile Örnek Uygulama
1. IRepository<T> Arayüzü
public interface IRepository<T>
where T : class
{
Task<T?> GetByIdAsync(
int id,
CancellationToken cancellationToken = default
);
Task<IEnumerable<T>> GetAllAsync(
CancellationToken cancellationToken = default
);
IQueryable<T> Query();
Task AddAsync(
T entity,
CancellationToken cancellationToken = default
);
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ı
using Microsoft.EntityFrameworkCore;
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,
CancellationToken cancellationToken = default
) =>
await _context
.Set<T>()
.FindAsync(new object[] { id }, cancellationToken);
public async Task<IEnumerable<T>> GetAllAsync(
CancellationToken cancellationToken = default
) => await _context.Set<T>().ToListAsync(cancellationToken);
public IQueryable<T> Query() => _context.Set<T>().AsNoTracking();
public async Task AddAsync(
T entity,
CancellationToken cancellationToken = default
) => await _context.Set<T>().AddAsync(entity, cancellationToken);
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)
.ThenBy(p => p.Id)
.ToListAsync(cancellationToken);
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 IUnitOfWork _unitOfWork;
public ProductService(IUnitOfWork unitOfWork)
{
_unitOfWork = unitOfWork;
}
public async Task<IEnumerable<Product>> GetActiveProductsAsync(
CancellationToken cancellationToken = default
)
{
return await _unitOfWork
.Products.Query()
.Where(p => p.IsActive)
.ToListAsync(cancellationToken);
}
public async Task AddProductAsync(
Product product,
CancellationToken cancellationToken = default
)
{
await _unitOfWork.Products.AddAsync(product, cancellationToken);
await _unitOfWork.CommitAsync(cancellationToken);
}
}
İleri Okuma ve Kaynaklar
Generic Repository için karar
Generic Repository seçerken ortak CRUD kodunun yanında provider ve query sınırını da değerlendirin. Buradaki IQueryable yaklaşımı EF-aware bir convenience wrapper’dır; veri erişim teknolojisini tamamen gizleyen bir domain sözleşmesi değildir.
- Ç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.
Tercihim, yalnız tekrar eden gerçek operasyonları ortaklaştırmak. Karmaşık read model için sırf her entity aynı generic arayüzü kullansın diye geniş bir sözleşme üretmeyin; application query ve transaction ihtiyacına göre sınır kurun.
Query(), IQueryable döndürdüğü için caller LINQ/EF Core extension’larına ve provider çevirisine bağımlıdır. Repository’yi mock etmek, servis içinde yazılan query’nin gerçek SQL davranışını kanıtlamaz. Sorguyu repository içinde çalıştırıp materialize edilmiş sonuç döndüren sözleşme başka bir test sınırı sağlar; bu seçenek ek query metotları gerektirir. EF Core test stratejisi bu farkı açıklıyor.
Örnekler .NET 10/EF Core 10, tek int key ve scoped AppDbContext varsayar. GetByIdAsync null dönebilir; caller missing record davranışını belirler. Query() bu örnekte AsNoTracking kullanır, GetAllAsync ise tracked entity’leri materialize eder. Büyük listelerde filter/projection/ordering/pagination kararını açıkça kurun. Query’yi context dispose edildikten sonra enumerate etmeyin.
Update detached entity’nin bütün güncellenebilir alanlarını Modified sayabilir; kısmi DTO ve concurrency token için ayrı kontrol gerekir. Add/Update/Delete kendiliğinden commit etmez. ProductService repository’yi UnitOfWork üzerinden alır; commit aynı context’in tracked değişikliklerini kaydeder. Unit of Work ve transaction sınırını dış servis işlemleriyle karıştırmayın.
IUnitOfWork.Products bu yazıdaki IRepository<Product> tipini sunmalı; CommitAsync(CancellationToken) aynı AppDbContext üzerinde SaveChangesAsync çağırmalıdır. AppDbContext, UnitOfWork ve ProductService için scoped DI kayıtları sağlayın. Genel Repository Pattern değerlendirmesi ek katmanın ne zaman anlamlı olduğunu ele alıyor.
