- Model sinifləri və
DbContextyazmaq, miqrasiya yaradıb tətbiq etmək - LINQ ilə verilənləri oxumaq, əlavə etmək, dəyişmək və silmək
- Əlaqələri qurmaq, N+1 problemindən və SQL inyeksiyasından qaçmaq
Aşağı səviyyəli ADO.NET ilə hər SQL sorğusunu əl ilə yazmaq və hər sütunu obyektə köçürmək lazımdır. Entity Framework Core (EF Core) — .NET-in rəsmi ORM-i — bu işi öz üzərinə götürür: siniflərini yazırsan, EF Core onlar üçün cədvəllər yaradır, LINQ sorğularını SQL-ə çevirir və nəticələri obyektlərə yığır. EF Core SQL Server, PostgreSQL, MySQL və SQLite ilə işləyir; nümunələrdə quraşdırma tələb etməyən SQLite-dan istifadə edəcəyik.
- 1Paketləri əlavə et
Microsoft.EntityFrameworkCore.Sqlite— SQLite provayderi,Microsoft.EntityFrameworkCore.Designisə miqrasiya alətləri üçün lazımdır. - 2dotnet-ef alətini quraşdır
dotnet tool install --global dotnet-efdotnet efəmrlərini əlavə edir; bunu kompüterdə bir dəfə etmək kifayətdir. - 3Modeli və DbContext-i yaz
Hər cədvəl bir sinifdir,
DbContextisə bazaya açılan «qapıdır». - 4Miqrasiya yarat və tətbiq et
dotnet ef migrations add InitialCreatesxemi C# faylı kimi yazır,dotnet ef database updateisə onu bazaya tətbiq edir.
dotnet add package Microsoft.EntityFrameworkCore.Sqlite
dotnet add package Microsoft.EntityFrameworkCore.Design
dotnet tool install --global dotnet-efModel və DbContext
EF Core razılaşmalarla işləyir: Id adlı xassə əsas açardır, ClassroomId + Classroom cütü xarici açardır, List<Student> isə «bir sinifdə çox şagird var» əlaqəsidir. required açar sözü Name-in NOT NULL sütun olacağını və obyekt yaradılanda mütləq verilməli olduğunu bildirir. DbContext-in DbSet<T> xassələri cədvəllərə uyğun gəlir, OnConfiguring isə hansı bazaya qoşulacağını deyir.
using Microsoft.EntityFrameworkCore;
public class Classroom
{
public int Id { get; set; } // primary key by convention
public required string Name { get; set; }
public List<Student> Students { get; set; } = []; // one classroom, many students
}
public class Student
{
public int Id { get; set; }
public required string Name { get; set; }
public int Score { get; set; }
public int ClassroomId { get; set; } // foreign key
public Classroom Classroom { get; set; } = null!;
}
public class SchoolContext : DbContext
{
public DbSet<Classroom> Classrooms => Set<Classroom>();
public DbSet<Student> Students => Set<Student>();
protected override void OnConfiguring(DbContextOptionsBuilder options)
=> options.UseSqlite("Data Source=school.db");
}Real tətbiqdə qoşulma sətrini OnConfiguring-ə yazmırlar: kontekst parametrləri konstruktordan alır — public class SchoolContext(DbContextOptions<SchoolContext> options) : DbContext(options) — sətir isə konfiqurasiya faylından gəlir (bunu növbəti dərsdə ASP.NET Core-da görəcəksən). DbContext yüngül və qısaömürlü obyektdir: adətən bir sorğu və ya bir iş vahidi üçün yaradılır və thread-lər üçün təhlükəsiz deyil, ona görə onu paralel tapşırıqlar arasında bölüşmək olmaz.
Miqrasiyalar
Miqrasiya — modeldəki dəyişikliyi bazaya daşıyan C# faylıdır. migrations add modeli əvvəlki vəziyyəti ilə müqayisə edir və Migrations/ qovluğunda Up (tətbiq et) və Down (geri qaytar) metodları olan sinif yaradır; database update isə hələ tətbiq olunmamış miqrasiyaları icra edir. Sinfə yeni xassə əlavə etdin? Yeni miqrasiya yarat — məsələn, AddEmail — və bazanı yenilə. Miqrasiyalar Git-də saxlanılır, ona görə komandada hər kəsin bazası eyni sxemə malik olur.
dotnet ef migrations add InitialCreate
dotnet ef database updateBuild started... Build succeeded. Done. To undo this action, use 'ef migrations remove' Build started... Build succeeded. Applying migration '20260927101500_InitialCreate'. Done.
LINQ to Entities: CRUD əməliyyatları
DbSet<T> üzərində yazılan LINQ sorğusu yaddaşda deyil, bazada icra olunur: EF Core ifadə ağacını SQL-ə çevirir (LINQ to Entities). Add və Remove dəyişiklikləri yalnız qeyd edir, SaveChangesAsync isə onları bir tranzaksiyada bazaya göndərir. Bazadan oxunmuş obyekt izlənilir (change tracking): onun xassəsini dəyişib SaveChangesAsync çağırmaq kifayətdir — EF Core UPDATE əmrini özü yazacaq.
await using var db = new SchoolContext();
var tenA = new Classroom { Name = "10A" };
tenA.Students.Add(new Student { Name = "Aysel", Score = 95 });
tenA.Students.Add(new Student { Name = "Leyla", Score = 88 });
db.Classrooms.Add(tenA);
await db.SaveChangesAsync(); // INSERTs; Id values are filled in
List<string> top = await db.Students
.Where(s => s.Score >= 90)
.OrderByDescending(s => s.Score)
.Select(s => s.Name)
.ToListAsync(); // the SQL runs here
Console.WriteLine(string.Join(", ", top));
Student leyla = await db.Students.SingleAsync(s => s.Name == "Leyla");
leyla.Score = 91; // the change tracker notices this
await db.SaveChangesAsync(); // UPDATE "Students" SET "Score" = ...
db.Students.Remove(leyla);
await db.SaveChangesAsync(); // DELETE
Console.WriteLine(await db.Students.CountAsync());Aysel 1
EF Core-un hansı SQL-i yaratdığını görmək faydalıdır: ToQueryString() sorğunu icra etmədən onun SQL mətnini qaytarır.
var query = db.Students
.Where(s => s.Score >= 80)
.OrderByDescending(s => s.Score)
.Select(s => s.Name);
Console.WriteLine(query.ToQueryString());SELECT "s"."Name" FROM "Students" AS "s" WHERE "s"."Score" >= 80 ORDER BY "s"."Score" DESC
Əlaqələr, Include və N+1 problemi
Əlaqəli verilənlər avtomatik yüklənmir: student.Classroom və ya classroom.Students sorğuda istənilməyibsə, boş qalır. Include(...) əlaqəli obyektləri eyni sorğuda gətirir. Çoxdan-çoxa əlaqə üçün hər iki tərəfdə kolleksiya yazmaq kifayətdir — aralıq cədvəli EF Core özü yaradır. Ən məşhur performans səhvi isə N+1-dir: siyahı üçün bir sorğu, sonra hər element üçün daha bir sorğu.
public class Course
{
public int Id { get; set; }
public required string Title { get; set; }
public List<Student> Students { get; set; } = [];
}
// in Student: public List<Course> Courses { get; set; } = [];
// EF Core creates the join table CourseStudent automatically
var withCourses = await db.Students
.Include(s => s.Courses) // load related data in the same query
.Where(s => s.Courses.Any(c => c.Title == "Math"))
.ToListAsync();var classes = await db.Classrooms.ToListAsync();
foreach (var c in classes)
{
// one more query for every classroom: the N+1 problem
int count = await db.Students.CountAsync(s => s.ClassroomId == c.Id);
Console.WriteLine($"{c.Name}: {count}");
}var classes = await db.Classrooms
.Select(c => new { c.Name, Count = c.Students.Count })
.ToListAsync(); // a single query with a subquery
foreach (var c in classes)
Console.WriteLine($"{c.Name}: {c.Count}");var list = await db.Students
.FromSqlRaw("SELECT * FROM Students WHERE Name = '" + name + "'")
.ToListAsync(); // SQL injection!var list = await db.Students
.FromSql($"SELECT * FROM Students WHERE Name = {name}")
.ToListAsync(); // {name} becomes a SQL parameterFromSql (EF Core 7-dən) interpolyasiya edilmiş qiymətləri SQL parametrlərinə çevirirƏsas fikirlər
- EF Core sinifləri cədvəllərə bağlayır:
DbContext+DbSet<T>;Idəsas açar,XxxIdxarici açardır. - Miqrasiyalar:
dotnet ef migrations addsxemi yazır,dotnet ef database updatetətbiq edir; onlar Git-də saxlanılır. DbSetüzərində LINQ SQL-ə çevrilir (ToQueryString()göstərir); dəyişiklikləriSaveChangesAsyncsaxlayır.- Əlaqəli verilənlər
Includevə ya proyeksiya ilə bir sorğuda yüklənir — N+1-dən qaç. - Xam SQL üçün
FromSql($"..."); oxumaq üçünAsNoTracking(), kütləvi dəyişiklik üçünExecuteUpdateAsync/ExecuteDeleteAsync.
Özünü yoxla
10 sual. Hər düzgün cavab XP qazandırır.