- Model sınıfları ve bir
DbContextyazmak, bir migration oluşturup uygulamak - LINQ ile veri okumak, eklemek, değiştirmek ve silmek
- İlişkileri kurmak, N+1 sorunundan ve SQL enjeksiyonundan kaçınmak
Alt düzey ADO.NET ile her SQL sorgusunu elle yazman ve her sütunu bir nesneye aktarman gerekir. .NET'in resmî ORM'i olan Entity Framework Core (EF Core) bu işi üstlenir: sınıflarını yazarsın, EF Core onlar için tablolar oluşturur, LINQ sorgularını SQL'e çevirir ve sonuçları nesnelere toplar. EF Core; SQL Server, PostgreSQL, MySQL ve SQLite ile çalışır; örneklerde kurulum gerektirmeyen SQLite'ı kullanacağız.
- 1Paketleri ekle
Microsoft.EntityFrameworkCore.SqliteSQLite sağlayıcısıdır,Microsoft.EntityFrameworkCore.Designise migration araçları için gerekir. - 2dotnet-ef aracını kur
dotnet tool install --global dotnet-ef,dotnet efkomutlarını ekler; bunu bilgisayarda bir kez yapmak yeterlidir. - 3Modeli ve DbContext'i yaz
Her tablo bir sınıftır;
DbContextise veritabanına açılan “kapı”dır. - 4Bir migration oluştur ve uygula
dotnet ef migrations add InitialCreateşemayı bir C# dosyası olarak yazar,dotnet ef database updateise onu veritabanına uygular.
dotnet add package Microsoft.EntityFrameworkCore.Sqlite
dotnet add package Microsoft.EntityFrameworkCore.Design
dotnet tool install --global dotnet-efModel ve DbContext
EF Core geleneklerle çalışır: Id adlı özellik birincil anahtardır, ClassroomId + Classroom çifti bir yabancı anahtardır, List<Student> ise “bir sınıfta çok öğrenci vardır” ilişkisidir. required anahtar sözcüğü, Name'in NOT NULL bir sütun olacağını ve nesne oluşturulurken mutlaka verilmesi gerektiğini belirtir. DbContext'in DbSet<T> özellikleri tablolara karşılık gelir, OnConfiguring ise hangi veritabanına bağlanılacağını söyler.
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");
}Gerçek bir uygulamada bağlantı dizesi OnConfiguring içine yazılmaz: bağlam ayarlarını yapıcıdan alır (public class SchoolContext(DbContextOptions<SchoolContext> options) : DbContext(options)), dize ise bir yapılandırma dosyasından gelir (bunu bir sonraki derste ASP.NET Core'da göreceksin). DbContext hafif ve kısa ömürlü bir nesnedir: genellikle tek bir istek ya da tek bir iş birimi için oluşturulur ve iş parçacığı güvenli değildir; bu yüzden paralel görevler arasında paylaşılmamalıdır.
Migration'lar
Bir migration, modeldeki bir değişikliği veritabanına taşıyan bir C# dosyasıdır. migrations add modeli önceki durumuyla karşılaştırır ve Migrations/ klasöründe Up (uygula) ve Down (geri al) metotları olan bir sınıf oluşturur; database update ise henüz uygulanmamış migration'ları çalıştırır. Bir sınıfa yeni bir özellik mi ekledin? Yeni bir migration oluştur (örneğin AddEmail) ve veritabanını güncelle. Migration'lar Git'te tutulur; böylece ekipteki herkesin veritabanı aynı şemaya sahip 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 işlemleri
Bir DbSet<T> üzerinde yazılan LINQ sorgusu bellekte değil, veritabanında çalışır: EF Core ifade ağacını SQL'e çevirir (LINQ to Entities). Add ve Remove değişiklikleri yalnızca kaydeder, SaveChangesAsync ise onları tek bir işlemde veritabanına gönderir. Veritabanından okunan bir nesne izlenir (change tracking): özelliğini değiştirip SaveChangesAsync çağırmak yeterlidir; UPDATE komutunu EF Core kendisi yazar.
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 hangi SQL'i ürettiğini görmek yararlıdır: ToQueryString(), sorguyu çalıştırmadan SQL metnini döndürü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
İlişkiler, Include ve N+1 sorunu
İlişkili veriler otomatik olarak yüklenmez: student.Classroom ya da classroom.Students sorguda istenmediyse boş kalır. Include(...), ilişkili nesneleri aynı sorguda getirir. Çoktan çoğa bir ilişki için iki tarafa da bir koleksiyon koymak yeterlidir; ara tabloyu EF Core kendisi oluşturur. En bilinen performans hatası ise N+1'dir: liste için bir sorgu, ardından her eleman için bir sorgu daha.
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'den beri), interpolasyonla verilen değerleri SQL parametrelerine dönüştürürÖnemli noktalar
- EF Core sınıfları tablolara eşler:
DbContext+DbSet<T>;Idbirincil anahtar,XxxIdyabancı anahtardır. - Migration'lar:
dotnet ef migrations addşemayı yazar,dotnet ef database updateuygular; Git'te tutulur. - Bir
DbSetüzerindeki LINQ, SQL'e dönüşür (ToQueryString()gösterir); değişiklikleriSaveChangesAsynckaydeder. - İlişkili verileri
Includeya da bir izdüşümle tek sorguda yükle; N+1'den kaçın. - Ham SQL için
FromSql($"..."); okuma içinAsNoTracking(), toplu değişiklik içinExecuteUpdateAsync/ExecuteDeleteAsync.
Kendini test et
10 soru. Her doğru cevap XP kazandırır.