LINQ bilan so'rov — Where, FirstOrDefault, Include, AsNoTracking

~25 daq · .NET Backend · Entity Framework Core — Ma'lumotlar Bazasi

Muammodan boshlaylik

Mahsulotlar sahifasi sekin — 3 soniya. SQL loglarini yoqsangiz:

SELECT * FROM "Mahsulotlar"                                      -- 50 000 qator, barcha ustunlar
SELECT * FROM "Kategoriyalar" WHERE "Id" = 1
SELECT * FROM "Kategoriyalar" WHERE "Id" = 2
SELECT * FROM "Kategoriyalar" WHERE "Id" = 1
... (yana 20 ta)

Kod esa "oddiy" ko'rinadi:

var hammasi = await db.Mahsulotlar.ToListAsync();             // butun jadval xotiraga
var sahifa = hammasi.Where(m => m.Faol).OrderBy(m => m.Narx).Take(20);
foreach (var m in sahifa)
    Console.WriteLine(m.Kategoriya.Nomi);                     // har biri uchun alohida so'rov

LINQ bir xil ko'rinadi — lekin qayerda bajarilishi (bazada yoki xotirada) natijani butunlay o'zgartiradi.


IQueryable va IEnumerable

IQueryable<Mahsulot> sorov = db.Mahsulotlar.Where(m => m.Faol);   // hali bajarilmadi — SQL quriladi
List<Mahsulot> natija = await sorov.ToListAsync();                 // shu yerda SQL yuboriladi
IQueryable<T> IEnumerable<T> (List)
Qayerda bajariladi Bazada — SQL ga o'giriladi Xotirada (C#)
Where, OrderBy, Take SQL ning bir qismi Barcha qatorlar yuklanganidan keyin

Qoida: filtrlash, saralash, sahifalash va Select — ToListAsync() dan oldin.

// ❌ 50 000 qator yuklanadi, keyin 20 tasi olinadi
var r = (await db.Mahsulotlar.ToListAsync()).Where(m => m.Faol).Take(20);

// ✅ bazadan faqat 20 qator
var r = await db.Mahsulotlar.Where(m => m.Faol).OrderBy(m => m.Narx).Take(20).ToListAsync();

Hayotiy o'xshatma

Ombordan buyurtma. Noto'g'ri yo'l — butun omborni yuk mashinasida uyga olib kelib, keyin kerakli 20 ta qutini tanlash. To'g'ri yo'l — omborchiga ro'yxat berish ("faqat qizil, kichik o'lchamdagi, birinchi 20 tasi"), u o'zi tanlab, faqat shularni jo'natadi. IQueryable — omborchiga ro'yxat; ToListAsync() — jo'natish.


Asosiy so'rovlar

// filtr
var arzonlar = await db.Mahsulotlar
    .Where(m => m.Narx < 100_000 && m.Faol)
    .ToListAsync(ct);

// matn qidiruv
var topilgan = await db.Mahsulotlar
    .Where(m => EF.Functions.ILike(m.Nomi, 
quot;%{q}%")) // PostgreSQL — katta-kichik harfsiz .ToListAsync(ct); // saralash va sahifalash var sahifa = await db.Mahsulotlar .OrderByDescending(m => m.YaratilganVaqt) .ThenBy(m => m.Id) .Skip((sahifaRaqam - 1) * hajm) .Take(hajm) .ToListAsync(ct); // bitta var m = await db.Mahsulotlar.FirstOrDefaultAsync(x => x.Id == id, ct); var yagona = await db.Mahsulotlar.SingleOrDefaultAsync(x => x.Slug == slug, ct); // agregatlar var jami = await db.Mahsulotlar.SumAsync(m => m.Narx * m.Qoldiq, ct); var bormi = await db.Mahsulotlar.AnyAsync(m => m.Qoldiq == 0, ct);

Async versiyalari (ToListAsync, FirstOrDefaultAsync, CountAsync) — Microsoft.EntityFrameworkCore dan.


Select — faqat kerakli ustunlar (projection)

var royxat = await db.Mahsulotlar
    .Where(m => m.Faol)
    .Select(m => new MahsulotRoyxatDto(m.Id, m.Nomi, m.Narx, m.Kategoriya.Nomi))
    .ToListAsync(ct);
SELECT m."Id", m."Nomi", m."Narx", k."Nomi"
FROM "Mahsulotlar" m
INNER JOIN "Kategoriyalar" k ON m."KategoriyaId" = k."Id"
WHERE m."Faol"
  • Faqat kerakli ustunlar — kam ma'lumot
  • m.Kategoriya.Nomi — EF Core o'zi JOIN qiladi
  • Change tracking yo'q — tezroq

[!TIP] O'qish uchun so'rovlar (API ga ma'lumot qaytarish) — deyarli har doim Select bilan DTO ga (45-dars). To'liq entity yuklash — faqat uni o'zgartirmoqchi bo'lganda.


Include — bog'liq ma'lumotlarni yuklash

var buyurtma = await db.Buyurtmalar
    .Include(b => b.Mijoz)
    .Include(b => b.Qatorlar)
        .ThenInclude(q => q.Mahsulot)
    .FirstOrDefaultAsync(b => b.Id == id, ct);

Navigation property lar (54-dars) standart holatda yuklanmaydi — Include bilan aniq so'rash kerak.

N+1 muammosi

var buyurtmalar = await db.Buyurtmalar.ToListAsync();        // 1 so'rov
foreach (var b in buyurtmalar)
    Console.WriteLine(b.Mijoz.Ism);                          // ❌ Mijoz yuklanmagan — null

Lazy loading yoqilgan bo'lsa (UseLazyLoadingProxies) — har bir buyurtma uchun alohida so'rov: 1 + N. 100 buyurtma — 101 so'rov.

var buyurtmalar = await db.Buyurtmalar
    .Select(b => new { b.Id, MijozIsmi = b.Mijoz.Ism, b.Summa })    // ✅ bitta so'rov
    .ToListAsync();

[!WARNING] Bir nechta kolleksiyani Include qilish (Include(b => b.Qatorlar).Include(b => b.Tolovlar)) — "kartezian portlash": SQL natijasida qatorlar soni ko'paytiriladi va so'rov juda sekinlashadi. Bunday holatda .AsSplitQuery() — har bir kolleksiya uchun alohida, lekin oldindan rejalashtirilgan so'rov.


AsNoTracking — faqat o'qish

var royxat = await db.Mahsulotlar
    .AsNoTracking()
    .Where(m => m.Faol)
    .ToListAsync(ct);

DbContext yuklangan ob'ektlarni kuzatmaydi — kamroq xotira, tezroq. O'zgartirilmaydigan ma'lumot uchun (ro'yxatlar, hisobotlar). Select bilan DTO ga — avtomatik kuzatilmaydi.

Butun DbContext uchun standart:

options.UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking);

Dinamik filtrlar

IQueryable<Mahsulot> q = db.Mahsulotlar.AsNoTracking();

if (filtr.KategoriyaId is not null)
    q = q.Where(m => m.KategoriyaId == filtr.KategoriyaId);
if (filtr.Min is not null)
    q = q.Where(m => m.Narx >= filtr.Min);
if (!string.IsNullOrWhiteSpace(filtr.Qidiruv))
    q = q.Where(m => EF.Functions.ILike(m.Nomi, 
quot;%{filtr.Qidiruv}%")); q = filtr.Saralash switch { "arzon" => q.OrderBy(m => m.Narx), "qimmat" => q.OrderByDescending(m => m.Narx), _ => q.OrderByDescending(m => m.YaratilganVaqt) }; var jami = await q.CountAsync(ct); var royxat = await q.Skip((filtr.Sahifa - 1) * filtr.Hajm).Take(filtr.Hajm) .Select(m => new MahsulotRoyxatDto(m.Id, m.Nomi, m.Narx, m.Kategoriya.Nomi)) .ToListAsync(ct);

IQueryable ga shartlarni bosqichma-bosqich qo'shish — natijada bitta SQL so'rov.


Xom SQL — kerak bo'lganda

var natija = await db.Mahsulotlar
    .FromSql(
quot;SELECT * FROM \"Mahsulotlar\" WHERE \"Narx\" < {maks}") .ToListAsync(ct);

FromSql interpolatsiyasi avtomatik parametrlarga aylanadi — SQL injection dan himoyalangan. FromSqlRaw + satr birlashtirish — xavfli.


Tez-tez uchraydigan xatolar

ToListAsync dan keyin filtrlash — butun jadval xotiraga.

SQL ga o'girib bo'lmaydigan metod:

db.Mahsulotlar.Where(m => MeningFormatim(m.Nomi) == "x")
// ❌ InvalidOperationException: The LINQ expression could not be translated

So'rovda faqat EF Core tushunadigan amallar; murakkab C# mantiq — natija olingandan keyin.

Include siz navigation property — null yoki N+1.


Xulosa

IQueryable — so'rov bazada bajariladi; Where, OrderBy, Skip/Take, Select — ToListAsync() dan oldin. Async metodlar: ToListAsync, FirstOrDefaultAsync, CountAsync, AnyAsync, SumAsync.

Select bilan DTO ga — faqat kerakli ustunlar, avtomatik JOIN, eng samarali. Include/ThenInclude — bog'liq ma'lumot; N+1 va kartezian portlashdan ehtiyot (AsSplitQuery). AsNoTracking — faqat o'qish uchun. Dinamik filtrlar — IQueryable ga bosqichma-bosqich. Xom SQL — FromSql interpolatsiyasi bilan.


Keyingi dars

54-dars: One-to-Many munosabat — navigation property, foreign key, Include