One-to-One munosabat — HasOne, WithOne, FK joylashuvi
Muammodan boshlaylik
Foydalanuvchi jadvalida 40 ta ustun: login ma'lumotlari, profil (bio, rasm, ijtimoiy tarmoqlar), sozlamalar (til, mavzu, bildirishnomalar), yetkazib berish manzili...
- Har bir login so'rovida bio va rasm URL lari ham yuklanadi
- Ko'p ustunlar ko'pchilik foydalanuvchilarda
null - Profil va xavfsizlik ma'lumotlari aralash — tasodifan profil API sida parol xeshi qaytib qolishi oson
Bir ob'ektga bitta qo'shimcha ob'ekt to'g'ri keladi — lekin ularni alohida saqlash kerak.
One-to-one nima?
Bitta yozuvga ko'pi bilan bitta bog'liq yozuv:
- Foydalanuvchi ↔ Profil
- Buyurtma ↔ Yetkazib berish ma'lumoti
- Xodim ↔ Ish o'rni (stol)
Hayotiy o'xshatma
Fuqaro va uning pasporti. Har bir fuqaroga bitta amaldagi pasport, har bir pasport — bitta fuqaroniki. Pasport alohida hujjat sifatida saqlanadi (alohida jadval), va unda egasining raqami yozilgan (foreign key) — fuqaroning tug'ilganlik guvohnomasida pasport raqami yozilmaydi.
Qaysi tomonda foreign key?
One-to-one da bir tomon — asosiy (principal), ikkinchisi — bog'liq (dependent). Foreign key bog'liq tomonda bo'ladi:
- Profil foydalanuvchisiz mavjud bo'la olmaydi → profil — bog'liq, FK profilda
- Foydalanuvchi profilsiz mavjud bo'la oladi → foydalanuvchi — asosiy
public class Foydalanuvchi
{
public int Id { get; set; }
public string Email { get; set; } = "";
public string ParolXesh { get; set; } = "";
public Profil? Profil { get; set; } // ixtiyoriy — bo'lmasligi mumkin
}
public class Profil
{
public int Id { get; set; }
public string? Bio { get; set; }
public string? RasmUrl { get; set; }
public string Til { get; set; } = "uz";
public int FoydalanuvchiId { get; set; } // FK — bog'liq tomonda
public Foydalanuvchi Foydalanuvchi { get; set; } = null!;
}
Fluent API
mb.Entity<Foydalanuvchi>()
.HasOne(f => f.Profil)
.WithOne(p => p.Foydalanuvchi)
.HasForeignKey<Profil>(p => p.FoydalanuvchiId) // <Profil> — FK qaysi entity da
.OnDelete(DeleteBehavior.Cascade);
One-to-one da EF Core FK qaysi tomonda ekanini o'zi har doim aniqlay olmaydi — HasForeignKey<T> bilan aniq ko'rsating. EF Core FoydalanuvchiId ga unique index qo'yadi — bitta foydalanuvchiga ikkinchi profil yozib bo'lmaydi.
Shared primary key
Bog'liq jadvalning primary key i — ayni paytda foreign key:
public class Profil
{
public int FoydalanuvchiId { get; set; } // PK va FK
public Foydalanuvchi Foydalanuvchi { get; set; } = null!;
public string? Bio { get; set; }
}
mb.Entity<Profil>().HasKey(p => p.FoydalanuvchiId);
mb.Entity<Foydalanuvchi>()
.HasOne(f => f.Profil)
.WithOne(p => p.Foydalanuvchi)
.HasForeignKey<Profil>(p => p.FoydalanuvchiId);
Alohida Id ustuni yo'q; profil ID si = foydalanuvchi ID si.
Ishlatish
// birga yaratish
var f = new Foydalanuvchi
{
Email = "lola@mail.uz",
ParolXesh = xesh,
Profil = new Profil { Bio = "Backend dasturchi", Til = "uz" }
};
db.Foydalanuvchilar.Add(f);
await db.SaveChangesAsync(ct);
// profil bilan o'qish
var u = await db.Foydalanuvchilar
.Include(x => x.Profil)
.FirstOrDefaultAsync(x => x.Id == id, ct);
Console.WriteLine(u?.Profil?.Bio ?? "Bio yo'q");
// login — profilsiz, faqat kerakli ustunlar
var login = await db.Foydalanuvchilar
.Where(x => x.Email == email)
.Select(x => new { x.Id, x.ParolXesh })
.FirstOrDefaultAsync(ct);
// profilni alohida yangilash
var profil = await db.Set<Profil>().FirstOrDefaultAsync(p => p.FoydalanuvchiId == userId, ct);
profil ??= db.Set<Profil>().Add(new Profil { FoydalanuvchiId = userId }).Entity;
profil.Bio = dto.Bio;
await db.SaveChangesAsync(ct);
Owned types — alohida jadvalsiz
Ma'lumot har doim egasi bilan birga bo'lsa va alohida identifikatsiyaga ega bo'lmasa (manzil, pul summasi) — owned type:
public class Manzil
{
public string Shahar { get; set; } = "";
public string Kocha { get; set; } = "";
public string? Uy { get; set; }
}
public class Buyurtma
{
public int Id { get; set; }
public Manzil YetkazishManzili { get; set; } = new();
}
mb.Entity<Buyurtma>().OwnsOne(b => b.YetkazishManzili);
Bazada — o'sha jadvalning ustunlari: YetkazishManzili_Shahar, YetkazishManzili_Kocha. Kodda — tartibli alohida klass. Include kerak emas — doim birga yuklanadi.
[!INFO] EF Core 8+ da complex types (
ComplexProperty) ham bor — owned type ga o'xshash, lekin qiymat semantikasi bilan (identifikatsiyasiz). Manzil, koordinata, pul kabi qiymat ob'ektlari uchun yangi va tavsiya etilgan usul.
Qachon alohida jadval
| Holat | Tanlov |
|---|---|
| Ma'lumot kamdan-kam kerak (bio, katta matn) | One-to-one — alohida jadval |
| Ko'pchilik yozuvlarda yo'q (ixtiyoriy) | One-to-one |
| Xavfsizlik bo'yicha ajratish (maxfiy va ochiq) | One-to-one |
| Har doim birga, identifikatsiyasiz (manzil) | Owned / complex type |
| Doim birga va kam ustun | Bitta jadvaldagi oddiy ustunlar |
[!TIP] One-to-one ni haddan tashqari ko'p ishlatmang: har bir alohida jadval — qo'shimcha JOIN. Ajratish — haqiqiy sabab bo'lganda: performance, ixtiyoriylik yoki xavfsizlik.
Tez-tez uchraydigan xatolar
FK tomoni noto'g'ri:
The dependent side could not be determined for the one-to-one relationship between 'Foydalanuvchi.Profil' and 'Profil.Foydalanuvchi'.
HasForeignKey<Profil>(...) bilan aniq ko'rsating.
Ikkinchi profil qo'shish — unique index buzilishi; mavjudini yangilang.
Include siz — Profil doim null ko'rinadi.
Xulosa
One-to-one — bitta yozuvga ko'pi bilan bitta bog'liq yozuv. FK — bog'liq (dependent) tomonda; Fluent API: HasOne().WithOne().HasForeignKey<Bog'liq>(), EF Core unique index qo'yadi. Shared primary key — bog'liq jadval PK si = FK.
Qo'shish — navigation orqali birga; o'qish — Include yoki Select (login da profil kerak emas). Har doim birga, identifikatsiyasiz ma'lumot — owned (OwnsOne) yoki complex type. Alohida jadval — performance, ixtiyoriylik yoki xavfsizlik uchun.
Keyingi dars
57-dars: Repository Pattern — IRepository<T>, GenericRepository, abstraktsiya