Məzmuna keç
Educora
İrəli24 dəq15 / 16

Entity Framework Core: verilənlər bazası ilə iş

EF Core ilə siniflərdən cədvəllər yarat: `DbContext`, miqrasiyalar, LINQ to Entities, CRUD əməliyyatları, əlaqələr və tipik performans tələləri.

Özünü yoxla
Bu dərsdə öyrənəcəksən
  • Model sinifləri və DbContext yazmaq, 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.

  1. 1
    Paketləri əlavə et

    Microsoft.EntityFrameworkCore.Sqlite — SQLite provayderi, Microsoft.EntityFrameworkCore.Design isə miqrasiya alətləri üçün lazımdır.

  2. 2
    dotnet-ef alətini quraşdır

    dotnet tool install --global dotnet-ef dotnet ef əmrlərini əlavə edir; bunu kompüterdə bir dəfə etmək kifayətdir.

  3. 3
    Modeli və DbContext-i yaz

    Hər cədvəl bir sinifdir, DbContext isə bazaya açılan «qapıdır».

  4. 4
    Miqrasiya yarat və tətbiq et

    dotnet ef migrations add InitialCreate sxemi C# faylı kimi yazır, dotnet ef database update isə onu bazaya tətbiq edir.

Terminal
dotnet add package Microsoft.EntityFrameworkCore.Sqlite
dotnet add package Microsoft.EntityFrameworkCore.Design
dotnet tool install --global dotnet-ef
Layihə qovluğunda icra olunur (paketlər nuget.org-dan yüklənir)

Model 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.

C#
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");
}
Model: iki sinif, bir əlaqə və kontekst

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.

Terminal
dotnet ef migrations add InitialCreate
dotnet ef database update
Gözlənilən nəticə
Build started...
Build succeeded.
Done. To undo this action, use 'ef migrations remove'
Build started...
Build succeeded.
Applying migration '20260927101500_InitialCreate'.
Done.
Nümunə nəticə: miqrasiyanın adı tarix-vaxt möhürü ilə başlayır

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.

C#
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());
Gözlənilən nəticə
Aysel
1
Boş baza üzərində nümunə nəticə: 90+ bal yalnız Ayseldədir, Leyla silindikdən sonra bir şagird qalır

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.

C#
var query = db.Students
    .Where(s => s.Score >= 80)
    .OrderByDescending(s => s.Score)
    .Select(s => s.Name);
Console.WriteLine(query.ToQueryString());
Gözlənilən nəticə
SELECT "s"."Name"
FROM "Students" AS "s"
WHERE "s"."Score" >= 80
ORDER BY "s"."Score" DESC
SQLite üçün nümunə nəticə; digər provayderlər bir qədər fərqli SQL yaradır

Ə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.

C#
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();
Çoxdan-çoxa əlaqə: şagird çox kursa, kurs isə çox şagirdə malikdir
N+1: 1 + N sorğu
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}");
}
Proyeksiya: bir sorğu
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}");
Təhlükəli: sətir birləşdirmə
var list = await db.Students
    .FromSqlRaw("SELECT * FROM Students WHERE Name = '" + name + "'")
    .ToListAsync();                  // SQL injection!
Təhlükəsiz: interpolyasiya parametrə çevrilir
var list = await db.Students
    .FromSql($"SELECT * FROM Students WHERE Name = {name}")
    .ToListAsync();                  // {name} becomes a SQL parameter
Xam SQL lazım olanda FromSql (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, XxxId xarici açardır.
  • Miqrasiyalar: dotnet ef migrations add sxemi yazır, dotnet ef database update tətbiq edir; onlar Git-də saxlanılır.
  • DbSet üzərində LINQ SQL-ə çevrilir (ToQueryString() göstərir); dəyişiklikləri SaveChangesAsync saxlayır.
  • Əlaqəli verilənlər Include və ya proyeksiya ilə bir sorğuda yüklənir — N+1-dən qaç.
  • Xam SQL üçün FromSql($"..."); oxumaq üçün AsNoTracking(), kütləvi dəyişiklik üçün ExecuteUpdateAsync/ExecuteDeleteAsync.

Özünü yoxla

10 sual. Hər düzgün cavab XP qazandırır.

1 / 10
Miqrasiyaları bazaya hansı əmr tətbiq edir?