CRUD — Add, Find, Update, Remove, SaveChangesAsync

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

Muammodan boshlaylik

Jadvallar tayyor (51-dars). Endi mahsulotlar API sining to'liq CRUD amallarini bazaga ulash kerak: yaratish, o'qish, yangilash, o'chirish. Har bir amal uchun EF Core da qaysi metod, qachon SaveChangesAsync kerak, va nega ba'zan o'zgarish saqlanmaydi?


Hayotiy o'xshatma

Bank operatsiyalari jurnali. Kassir kun davomida operatsiyalarni qoralama daftarga yozadi (change tracking): "Aliga 100 ming", "Valining hisobini yopish". Kun oxirida bosh buxgalter hammasini bir marta asosiy kitobga ko'chiradi (SaveChangesAsync). Ko'chirishda xato bo'lsa — butun kun yozuvlari bekor qilinadi, yarim yozilgan holat bo'lmaydi.


Create — qo'shish

var m = new Mahsulot
{
    Nomi = "Choynak",
    Narx = 120_000m,
    Qoldiq = 10,
    KategoriyaId = 2,
};

db.Mahsulotlar.Add(m);              // faqat kuzatuvga qo'shildi
await db.SaveChangesAsync(ct);      // INSERT — shu yerda

Console.WriteLine(m.Id);            // 42 — baza bergan ID avtomatik to'ldirildi
db.Mahsulotlar.AddRange(mahsulot1, mahsulot2, mahsulot3);
await db.SaveChangesAsync(ct);      // bitta tranzaksiyada

Read — o'qish

var m = await db.Mahsulotlar.FindAsync([id], ct);                    // primary key bo'yicha
var b = await db.Mahsulotlar.FirstOrDefaultAsync(x => x.Nomi == "Choynak", ct);
var royxat = await db.Mahsulotlar.Where(x => x.Faol).ToListAsync(ct);
bool bormi = await db.Mahsulotlar.AnyAsync(x => x.Nomi == nomi, ct);
int soni = await db.Mahsulotlar.CountAsync(ct);
  • FindAsync — avval DbContext ichidagi kuzatilayotgan ob'ektlardan, keyin bazadan qidiradi
  • Yo'q bo'lsa — null
  • LINQ so'rovlari — 53-dars

Update — yangilash

Usul 1: yuklab, o'zgartirib, saqlash

var m = await db.Mahsulotlar.FindAsync([id], ct);
if (m is null) return NotFound();

m.Narx = dto.Narx;
m.Qoldiq = dto.Qoldiq;

await db.SaveChangesAsync(ct);      // UPDATE Mahsulotlar SET Narx = ..., Qoldiq = ... WHERE Id = ...

Update() chaqirish shart emas — DbContext ob'ektni kuzatayapti va nima o'zgarganini o'zi biladi. SQL da faqat o'zgargan ustunlar.

Usul 2: ExecuteUpdate — yuklamasdan (EF Core 7+)

int yangilandi = await db.Mahsulotlar
    .Where(x => x.KategoriyaId == 2)
    .ExecuteUpdateAsync(s => s
        .SetProperty(x => x.Narx, x => x.Narx * 1.1m)
        .SetProperty(x => x.YangilanganVaqt, DateTime.UtcNow), ct);
// UPDATE Mahsulotlar SET Narx = Narx * 1.1, ... WHERE KategoriyaId = 2

Ko'p qatorni bitta SQL bilan — ob'ektlarni xotiraga yuklamasdan. SaveChangesAsync kerak emas — darhol bajariladi.

[!WARNING] ExecuteUpdate/ExecuteDelete change tracking ni chetlab o'tadi: DbContext dagi allaqachon yuklangan ob'ektlar eski qiymatlarni ko'rsatishda davom etadi, va SaveChanges ga bog'liq mexanizmlar (masalan, SaveChanges override idagi audit maydonlari) ishlamaydi. Ommaviy amallar uchun ishlating, oddiy CRUD uchun — 1-usul.


Delete — o'chirish

var m = await db.Mahsulotlar.FindAsync([id], ct);
if (m is null) return NotFound();

db.Mahsulotlar.Remove(m);
await db.SaveChangesAsync(ct);       // DELETE FROM Mahsulotlar WHERE Id = ...
int ochirildi = await db.Mahsulotlar
    .Where(x => x.Qoldiq == 0 && x.YaratilganVaqt < DateTime.UtcNow.AddYears(-2))
    .ExecuteDeleteAsync(ct);         // yuklamasdan

Soft delete — global filtr

public class Mahsulot
{
    // ...
    public bool Ochirilgan { get; set; }
}

protected override void OnModelCreating(ModelBuilder mb)
{
    mb.Entity<Mahsulot>().HasQueryFilter(m => !m.Ochirilgan);
}
m.Ochirilgan = true;                 // o'chirish o'rniga
await db.SaveChangesAsync(ct);

await db.Mahsulotlar.ToListAsync();                           // o'chirilganlar avtomatik chiqarilmaydi
await db.Mahsulotlar.IgnoreQueryFilters().ToListAsync();      // admin uchun — hammasi

Entity holatlari

var m = new Mahsulot { Nomi = "Kosa" };
Console.WriteLine(db.Entry(m).State);    // Detached

db.Add(m);
Console.WriteLine(db.Entry(m).State);    // Added

await db.SaveChangesAsync();
Console.WriteLine(db.Entry(m).State);    // Unchanged

m.Narx = 35000;
Console.WriteLine(db.Entry(m).State);    // Modified
Holat SaveChanges da
Added INSERT
Modified UPDATE
Deleted DELETE
Unchanged Hech narsa
Detached Kuzatilmaydi

To'liq servis

public class MahsulotService(DokonDbContext db) : IMahsulotService
{
    public async Task<MahsulotDto?> OlAsync(int id, CancellationToken ct) =>
        await db.Mahsulotlar
            .Where(m => m.Id == id)
            .Select(m => new MahsulotDto(m.Id, m.Nomi, m.Narx, m.Qoldiq > 0, m.Kategoriya.Nomi))
            .FirstOrDefaultAsync(ct);

    public async Task<MahsulotDto> YaratAsync(MahsulotYaratDto dto, CancellationToken ct)
    {
        var m = new Mahsulot { Nomi = dto.Nomi.Trim(), Narx = dto.Narx, Qoldiq = dto.Qoldiq, KategoriyaId = dto.KategoriyaId };
        db.Mahsulotlar.Add(m);
        await db.SaveChangesAsync(ct);
        return (await OlAsync(m.Id, ct))!;
    }

    public async Task<bool> YangilaAsync(int id, MahsulotYangilaDto dto, CancellationToken ct)
    {
        var m = await db.Mahsulotlar.FindAsync([id], ct);
        if (m is null) return false;
        m.Nomi = dto.Nomi.Trim();
        m.Narx = dto.Narx;
        m.Qoldiq = dto.Qoldiq;
        await db.SaveChangesAsync(ct);
        return true;
    }

    public async Task<bool> OchirAsync(int id, CancellationToken ct) =>
        await db.Mahsulotlar.Where(m => m.Id == id).ExecuteDeleteAsync(ct) > 0;
}

Xatolarni qayta ishlash

try
{
    await db.SaveChangesAsync(ct);
}
catch (DbUpdateException ex) when (ex.InnerException is PostgresException { SqlState: PostgresErrorCodes.UniqueViolation })
{
    return Conflict("Bu nomli kategoriya allaqachon mavjud");
}

Unique index buzilishi — 409 Conflict. Foreign key buzilishi (bog'liq yozuv bor) — ham DbUpdateException.

[!TIP] Bitta so'rov ichida bir nechta o'zgarish — bitta SaveChangesAsync: ular bitta tranzaksiyada saqlanadi. Har bir o'zgarishdan keyin alohida SaveChanges — sekinroq va yarim saqlangan holat xavfi.


Tez-tez uchraydigan xatolar

SaveChangesAsync ni unutish — o'zgarishlar bazaga tushmaydi, xato ham yo'q.

AsNoTracking bilan yuklab, o'zgartirish — ob'ekt kuzatilmaydi, SaveChanges hech narsa qilmaydi.

Foydalanuvchi bergan entity ni Update() qilish:

db.Update(dtodanYasalganEntity);   // ❌ BARCHA ustunlar yoziladi, yuborilmaganlari — standart qiymatlar

Yuklab, kerakli maydonlarni o'zgartiring.


Xulosa

Create — Add/AddRange + SaveChangesAsync, ID avtomatik to'ldiriladi. Read — FindAsync (primary key), FirstOrDefaultAsync, ToListAsync, AnyAsync. Update — yuklab, property ni o'zgartirib, SaveChangesAsync (change tracking, Update() shart emas); ommaviy — ExecuteUpdateAsync. Delete — Remove + saqlash yoki ExecuteDeleteAsync; soft delete — HasQueryFilter.

Entity holatlari: Added, Modified, Deleted, Unchanged, Detached. Bitta so'rovda — bitta SaveChangesAsync (bitta tranzaksiya). DbUpdateException — unique/foreign key xatolari.


Keyingi dars

53-dars: LINQ bilan so'rov — Where, FirstOrDefault, Include, AsNoTracking