Repository Pattern — IRepository<T>, GenericRepository, abstraktsiya
Muammodan boshlaylik
Controller va servislar to'g'ridan-to'g'ri DbContext bilan ishlaydi:
public class BuyurtmaService(DokonDbContext db)
{
public async Task<decimal> MijozJamiAsync(int mijozId) =>
await db.Buyurtmalar
.Where(b => b.MijozId == mijozId && b.Holat != BuyurtmaHolati.Bekor && !b.Ochirilgan)
.SumAsync(b => b.Summa);
}
Xuddi shu "mijozning faol buyurtmalari" filtri 6 ta joyda takrorlangan — birida !b.Ochirilgan unutilgan. Servisni testlash uchun haqiqiy baza yoki EF Core ning murakkab mock lari kerak.
Repository nima?
Repository — ma'lumotlarga kirishni bir joyga yig'adigan qatlam. Servis "qanday" saqlanishini (EF Core, SQL, API) bilmaydi — faqat "nima" kerakligini so'raydi:
public interface IBuyurtmaRepository
{
Task<Buyurtma?> OlAsync(int id, CancellationToken ct = default);
Task<List<Buyurtma>> MijozFaolBuyurtmalariAsync(int mijozId, CancellationToken ct = default);
Task<decimal> MijozJamiAsync(int mijozId, CancellationToken ct = default);
void Qosh(Buyurtma b);
Task SaqlaAsync(CancellationToken ct = default);
}
public class BuyurtmaRepository(DokonDbContext db) : IBuyurtmaRepository
{
private IQueryable<Buyurtma> Faollar(int mijozId) =>
db.Buyurtmalar.Where(b => b.MijozId == mijozId && b.Holat != BuyurtmaHolati.Bekor);
public Task<Buyurtma?> OlAsync(int id, CancellationToken ct) =>
db.Buyurtmalar.Include(b => b.Qatorlar).FirstOrDefaultAsync(b => b.Id == id, ct);
public Task<List<Buyurtma>> MijozFaolBuyurtmalariAsync(int mijozId, CancellationToken ct) =>
Faollar(mijozId).OrderByDescending(b => b.Sana).ToListAsync(ct);
public Task<decimal> MijozJamiAsync(int mijozId, CancellationToken ct) =>
Faollar(mijozId).SumAsync(b => b.Summa, ct);
public void Qosh(Buyurtma b) => db.Buyurtmalar.Add(b);
public Task SaqlaAsync(CancellationToken ct) => db.SaveChangesAsync(ct);
}
builder.Services.AddScoped<IBuyurtmaRepository, BuyurtmaRepository>();
public class BuyurtmaService(IBuyurtmaRepository repo)
{
public Task<decimal> MijozJamiAsync(int mijozId, CancellationToken ct) => repo.MijozJamiAsync(mijozId, ct);
}
Filtr — bitta joyda; servis testda soxta repository bilan ishlaydi (77-dars).
Hayotiy o'xshatma
Kutubxonachi. Siz arxivga o'zingiz tushib, javonlarni titkilamaysiz — kutubxonachiga "Qodiriyning barcha kitoblari" deysiz. U arxiv qanday tuzilganini, qaysi kartotekadan qidirishni biladi. Arxiv boshqa binoga ko'chsa yoki elektron bo'lsa — siz bilan muomala o'zgarmaydi.
Generic repository
Barcha entity lar uchun umumiy CRUD:
public interface IEntity { int Id { get; } }
public interface IRepository<T> where T : class, IEntity
{
Task<T?> OlAsync(int id, CancellationToken ct = default);
Task<List<T>> HammasiAsync(CancellationToken ct = default);
void Qosh(T entity);
void Ochir(T entity);
Task SaqlaAsync(CancellationToken ct = default);
}
public class Repository<T>(DokonDbContext db) : IRepository<T> where T : class, IEntity
{
protected readonly DbSet<T> Set = db.Set<T>();
public Task<T?> OlAsync(int id, CancellationToken ct) => Set.FirstOrDefaultAsync(e => e.Id == id, ct);
public Task<List<T>> HammasiAsync(CancellationToken ct) => Set.AsNoTracking().ToListAsync(ct);
public void Qosh(T entity) => Set.Add(entity);
public void Ochir(T entity) => Set.Remove(entity);
public Task SaqlaAsync(CancellationToken ct) => db.SaveChangesAsync(ct);
}
builder.Services.AddScoped(typeof(IRepository<>), typeof(Repository<>)); // barcha T lar uchun
public class KategoriyaService(IRepository<Kategoriya> repo) { ... }
Maxsus so'rovlar — generic dan meros olgan aniq repository da:
public class MahsulotRepository(DokonDbContext db) : Repository<Mahsulot>(db), IMahsulotRepository
{
public Task<List<Mahsulot>> ArzonlarAsync(decimal maks, CancellationToken ct) =>
Set.Where(m => m.Narx < maks).ToListAsync(ct);
}
Unit of Work
Bir nechta repository o'zgarishini bitta tranzaksiyada saqlash:
public interface IUnitOfWork
{
IMahsulotRepository Mahsulotlar { get; }
IBuyurtmaRepository Buyurtmalar { get; }
Task<int> SaqlaAsync(CancellationToken ct = default);
}
var m = await uow.Mahsulotlar.OlAsync(id, ct);
m!.Qoldiq -= soni;
uow.Buyurtmalar.Qosh(new Buyurtma { ... });
await uow.SaqlaAsync(ct); // ikkalasi — bitta SaveChanges, bitta tranzaksiya
[!INFO] EF Core ning
DbContexti o'zi Unit of Work (change tracking +SaveChangestranzaksiyasi),DbSet<T>esa — o'zi repository. Shuning uchun EF Core ustiga generic repository + UoW qatlami qo'shishning foydasi haqida jamoalar orasida munozara bor.
Qachon kerak — munozarali savol
| Repository foydasi | Kamchiligi |
|---|---|
| So'rovlar bir joyda, takrorlanmaydi | Qo'shimcha qatlam va kod |
| Servislarni bazasiz test qilish | Generic IRepository<T> LINQ kuchini yashiradi |
| Ma'lumot manbasini almashtirish (nazariy) | Amalda EF Core ni almashtirish kam bo'ladi |
Domen tilidagi metodlar (MijozFaolBuyurtmalari) |
IQueryable qaytarilsa — abstraktsiya "teshik" |
Amaliy tavsiyalar:
- Kichik loyiha, oddiy CRUD —
DbContextni servislarda to'g'ridan-to'g'ri ishlating - Takrorlanadigan murakkab so'rovlar, domen mantiqi — aniq (generic bo'lmagan) repository lar:
IBuyurtmaRepositorydomen tilidagi metodlar bilan - Generic
IRepository<T>faqatGetById/Add/Removeuchun — ko'pincha ortiqcha
[!WARNING] Repository dan
IQueryable<T>qaytarmang (IQueryable<Buyurtma> Hammasi()) — chaqiruvchi istalgan so'rov quradi, va repository abstraktsiyasi ma'nosini yo'qotadi: EF Core ga bog'liqlik baribir servisga "oqib" chiqadi. Natija —List<T>,T?yoki DTO.
Testda
public class SoxtaBuyurtmaRepository : IBuyurtmaRepository
{
private readonly List<Buyurtma> _r = new();
public Task<decimal> MijozJamiAsync(int mijozId, CancellationToken ct = default) =>
Task.FromResult(_r.Where(b => b.MijozId == mijozId).Sum(b => b.Summa));
// ...
}
Yoki Moq bilan (77-dars). Repository ning o'zini esa — haqiqiy baza bilan integration testda (78-dars).
Tez-tez uchraydigan xatolar
Har bir repository metodi ichida SaveChanges — bir nechta o'zgarish bitta tranzaksiyada saqlanmaydi; saqlash — servis/UoW darajasida.
IQueryable qaytarish — abstraktsiya buziladi.
Repository lar turli DbContext nusxalarida — Scoped ro'yxat va bitta so'rovda bitta DbContext (38-dars).
Xulosa
Repository — ma'lumotlarga kirishni bir joyga yig'adi: servis "nima" kerakligini so'raydi, "qanday" ni bilmaydi; so'rovlar takrorlanmaydi, servislarni bazasiz test qilish oson. Generic IRepository<T> — umumiy CRUD, AddScoped(typeof(IRepository<>), typeof(Repository<>)); maxsus so'rovlar — aniq repository da.
Unit of Work — bir nechta o'zgarish bitta tranzaksiyada; EF Core DbContext o'zi UoW va DbSet o'zi repository. Kichik loyihada DbContext yetarli; murakkab domen — aniq repository lar. IQueryable qaytarmang; SaveChanges — servis darajasida.
Keyingi bo'lim
58-dars: Password Hashing — BCrypt.Net, hash va verify, SALT_ROUNDS