CRUD — Add, Find, Update, Remove, SaveChangesAsync
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/ExecuteDeletechange tracking ni chetlab o'tadi: DbContext dagi allaqachon yuklangan ob'ektlar eski qiymatlarni ko'rsatishda davom etadi, vaSaveChangesga bog'liq mexanizmlar (masalan,SaveChangesoverride 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 alohidaSaveChanges— 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