EF Core ile veri erişim katmanı yazmak: bağlam, migration ve sorgu örnekleri
Beceri rehberi

EF Core ile veri erişim katmanı yazmak: bağlam, migration ve sorgu örnekleri

Hazırlayan: Mindset Institute EditörlüğüGüncellendi: 7 dk okuma

EF Core ile DbContext, model, migration, CRUD sorguları ve ASP.NET Core kaydı: örnek kodlarla veri erişim katmanını yazma adımları ve sık hatalar.

Entity Framework Core veri erişim katmanında ne işe yarar?

Entity Framework Core (EF Core), .NET uygulamalarında veritabanı tablolarını C# sınıfları üzerinden okumayı ve yazmayı sağlayan nesne-ilişkisel eşleme kütüphanesidir. SQL yazmak yerine sınıf, özellik ve LINQ sorgusu yazarsınız; EF Core bunu seçtiğiniz veritabanı sağlayıcısının anlayacağı komutlara çevirir.

Veri erişim katmanının merkezinde iki kavram vardır. DbContext, veritabanıyla yapılan bir oturumu temsil eder ve değişiklikleri izler. DbSet, her varlık türü için tabloya karşılık gelen koleksiyondur. Microsoft belgelerine göre bir DbContext örneği tek bir iş birimi için tasarlanmıştır; yani ömrü genellikle çok kısadır: oluştur, varlıkları getir ya da ekle, değiştir, SaveChanges çağır, nesneyi bırak.

EF Core hangi veritabanıyla konuşacağını sağlayıcı paketi belirler. Örneğin SQL Server, SQLite ya da PostgreSQL için ayrı sağlayıcı paketleri vardır ve her DbContext örneği yalnızca bir sağlayıcıyla yapılandırılır. Bu yüzden ilk karar, hangi veritabanı kullanılacağı ve ona uygun paketin projeye eklenmesidir.

Doğrudan SQL yazmaya göre kazancı, tip güvenli sorgular, model değişikliklerinin migration ile izlenebilmesi ve değişiklik izleme sayesinde güncelleme kodunun kısalmasıdır. Bedeli ise üretilen SQL'i anlamadan yazılan sorgunun yavaş çalışabilmesidir; bu yüzden aşağıdaki bölümlerde sorgu davranışına da değinilir.

Projeyi kurma: paketler ve ilk model nasıl yazılır?

Basit bir konsol uygulamasıyla başlamak mekaniği öğrenmek için yeterlidir. Microsoft'un başlangıç öğreticisi SQLite sağlayıcısını kullanır. Komutlar şöyledir:

dotnet new console -o EFGetStarted cd EFGetStarted dotnet add package Microsoft.EntityFrameworkCore.Sqlite

Ardından bağlam ve varlık sınıfları tanımlanır:

public class BloggingContext : DbContext { public DbSet<Blog> Blogs { get; set; } public DbSet<Post> Posts { get; set; }

protected override void OnConfiguring(DbContextOptionsBuilder options) => options.UseSqlite("Data Source=blogging.db"); }

public class Blog { public int BlogId { get; set; } public string Url { get; set; } public List<Post> Posts { get; } = new(); }

public class Post { public int PostId { get; set; } public string Title { get; set; } public string Content { get; set; } public int BlogId { get; set; } public Blog Blog { get; set; } }

Burada Blog ile Post arasındaki ilişki, yabancı anahtar özelliği (BlogId) ve gezinme özellikleri (Blog, Posts) sayesinde EF Core tarafından kural gereği algılanır. Üretim uygulamasında bağlantı dizesi koda gömülmez; yapılandırmadan okunur. Öğreticideki sabit yol yalnızca sadelik içindir.

Migration ile şema nasıl oluşturulur ve güncellenir?

Modeli yazdıktan sonra veritabanı şeması migration ile oluşturulur. İlk kurulumda araçlar yüklenir ve ilk migration üretilir:

dotnet tool install --global dotnet-ef dotnet add package Microsoft.EntityFrameworkCore.Design dotnet ef migrations add InitialCreate dotnet ef database update

İlk komut dotnet ef aracını kurar, sonraki komut tasarım zamanı paketini projeye ekler. migrations add komutu projede Migrations klasörü oluşturur ve modelin o anki hâliyle önceki anlık görüntüyü karşılaştırarak farkı yazar. database update komutu veritabanını oluşturur ve migration'ı uygular. Visual Studio kullananlar için Package Manager Console karşılıkları Add-Migration ve Update-Database komutlarıdır.

Model değiştiğinde aynı döngü tekrarlanır. Örneğin Blog sınıfına bir oluşturulma zamanı özelliği eklendiğinde:

dotnet ef migrations add AddBlogCreatedTimestamp dotnet ef database update

EF Core uygulanmış migration'ları veritabanındaki özel bir geçmiş tablosunda tutar ve yalnızca yenisini uygular. Migration'a anlamlı bir ad vermek, ileride geçmişi okumayı kolaylaştırır.

Önemli bir kural: üretilen migration dosyasını uygulamadan önce okuyun. Microsoft belgesi, bir sütunun yeniden adlandırılmak istenirken silinip yeniden oluşturulabildiğini ve bunun veri kaybına yol açabileceğini hatırlatır. Üretim ortamında database update doğrudan önerilmez; denetlenebilen SQL betiği ya da otomasyon için migration paketi (bundle) tercih edilir.

Temel okuma, ekleme, güncelleme ve silme nasıl yazılır?

Microsoft'un öğreticisindeki sıra, veri erişim katmanının çekirdeğini gösterir:

using var db = new BloggingContext();

// Ekleme db.Add(new Blog { Url = "http://blogs.msdn.com/adonet" }); await db.SaveChangesAsync();

// Okuma var blog = await db.Blogs .OrderBy(b => b.BlogId) .FirstAsync();

// Güncelleme blog.Url = "https://devblogs.microsoft.com/dotnet"; blog.Posts.Add(new Post { Title = "Hello World", Content = "I wrote an app using EF Core!" }); await db.SaveChangesAsync();

// Silme db.Remove(blog); await db.SaveChangesAsync();

Güncellemede açıkça bir Update çağrısı yoktur: sorgudan gelen nesne izlendiği için özelliği değiştirmek ve SaveChangesAsync çağırmak yeterlidir. Sorgular varsayılan olarak izlemelidir. Yalnızca okunacak sonuçlarda AsNoTracking kullanmak, izleme bilgisi kurulmadığı için genellikle daha hızlıdır:

var blogs = await db.Blogs .AsNoTracking() .ToListAsync();

İlişkili veriyi getirmek için Include kullanılır:

var blogs = await db.Blogs .Include(blog => blog.Posts) .ToListAsync();

Daha derin ilişkiler için ThenInclude zincirlenir. Belgede belirtildiği gibi, bir koleksiyonu tek sorguda yüklemek performans sorunlarına yol açabilir; büyük koleksiyonlarda bölünmüş sorgu seçeneği değerlendirilmelidir.

ASP.NET Core içinde DbContext nasıl kaydedilir ve kullanılır?

Web uygulamasında her HTTP isteği çoğunlukla tek bir iş birimine karşılık gelir; bu yüzden bağlamın ömrünü isteğe bağlamak iyi bir varsayılandır. Program.cs içinde bağlam bağımlılık enjeksiyonuna kaydedilir:

var connectionString = builder.Configuration.GetConnectionString("DefaultConnection") ?? throw new InvalidOperationException("Connection string not found.");

builder.Services.AddDbContext<ApplicationDbContext>(options => options.UseSqlServer(connectionString));

Bağlam sınıfı, DbContextOptions türünde parametre alan bir kurucu sunar:

public class ApplicationDbContext : DbContext { public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) : base(options) { } }

Denetleyici ya da servis, bağlamı kurucu enjeksiyonuyla alır. AddDbContext bağlamı varsayılan olarak kapsamlı (scoped) ömürle kaydeder; istek bitince nesne bırakılır. Blazor Server gibi istek kapsamının iş birimiyle örtüşmediği uygulamalarda AddDbContextFactory ile fabrika deseni kullanılır ve fabrikadan alınan bağlam uygulama tarafından bırakılmalıdır.

Katman tasarımında bir soru sık sorulur: bağlamı doğrudan servislerde mi kullanmalı, ek bir depo (repository) katmanı mı yazmalı? DbContext zaten iş birimi ve depo davranışını içerir. Küçük ve orta uygulamalarda servisin bağlamı doğrudan kullanması yeterli olabilir; test, sorgu yeniden kullanımı ya da veri kaynağı değişimi gibi somut ihtiyaç doğunca ek soyutlama düşünülür.

Veri erişim katmanında en sık yapılan hatalar nelerdir?

  • Paralel kullanım: DbContext iş parçacığı güvenli değildir. Aynı bağlam örneğiyle aynı anda birden fazla işlem başlatmak, belgede belirtildiği gibi InvalidOperationException ya da kötü durumda veri bozulmasına yol açabilir. Asenkron çağrılar bekletilmeden bırakılmamalı, her çağrı hemen await edilmelidir.
  • Uzun ömürlü bağlam: Bağlamı uygulama boyunca tek örnek olarak tutmak izlenen varlıkların birikmesine ve bayat veriye neden olur. Kısa ömürlü kullanın.
  • Gereksiz izleme: Yalnız okunan liste sorgularında AsNoTracking kullanılmaması bellek ve zaman kaybettirir.
  • Eksik ya da aşırı Include: İlişkili veri getirilmezse gezinme özelliği boş kalır; gereğinden fazla getirilirse sorgu şişer. Ekranın ihtiyaç duyduğu alanlar için Select ile yalnız gerekli sütunları çekmek çoğu zaman daha iyidir.
  • Migration'ı okumadan uygulamak: Üretilen dosyayı gözden geçirmemek veri kaybı riski taşır.
  • Silinmiş migration dosyaları: Migration dosyalarını elle silmek anlık görüntüyle uyumsuzluk yaratır. Geri almak için migrations remove komutu kullanılır.
  • Bağlantı dizesini koda gömmek: Gizli bilgi kaynak kodda kalır. Yapılandırma ve ortam değişkeni ya da gizli bilgi deposu kullanılmalıdır.

Hata ayıklarken üretilen SQL'e bakmayı alışkanlık edinin. LINQ sorgusunun hangi SQL'e dönüştüğünü bilmek, yavaş sorguların ve beklenmeyen sonuçların nedenini hızla gösterir.

Bu beceri Mindset programlarında nerede çalışılır?

Mindset kataloğundaki ".NET ile Web Uygulamaları" eğitimi, C# bilen ve web uygulaması geliştirmek isteyen yazılımcılar ile .NET ekiplerine yöneliktir. Modül başlıkları arasında .NET ve ASP.NET Core ekosistemi, MVC mimarisi ve Web API tasarımı, kimlik doğrulama ve güvenlik, test, konteynerizasyon ve Azure dağıtımı yer alır. Bu sayfada anlatılan bağlam, model ve migration konuları "Entity Framework Core ile Veri Katmanı" modülünün alanına girer.

Kurumsal bir yazılım ekibi için, veri katmanı ile Web API katmanı aynı program içinde birlikte işlendiği için ekip ortak bir mimari dil kazanabilir.

İlgili Mindset programı

.NET ile Web Uygulamaları

Sık sorulanlar

Sık sorulan sorular

EF Core ile Dapper ya da düz SQL arasında nasıl seçim yapılır?

EF Core model odaklı çalışır, değişiklik izler ve migration sunar; geliştirme hızı ve tip güvenliği sağlar. Düz SQL ya da hafif kütüphaneler, üretilen sorgu üzerinde tam denetim istendiğinde ve çok özel performans ihtiyaçlarında düşünülür. Aynı projede ikisini birlikte kullanmak da mümkündür; sık görülen yaklaşım, günlük işlerde EF Core, yoğun raporlama sorgularında elle yazılmış SQL kullanmaktır.

Migration geri alınır mı?

Henüz uygulanmamış son migration için migrations remove komutu kullanılır. Uygulanmış bir migration'a dönmek için database update komutuna hedef migration adı verilir; bu işlem veri kaybına yol açabilir, dolayısıyla önce yedek alınmalı ve etkiler incelenmelidir. Üretimde geri alma için SQL betiği ya da paket kullanmak, uygulanacak komutları önceden görmeyi sağlar.

DbContext neden kısa ömürlü olmalı?

Bağlam, sorgudan gelen varlıkları izler. Uzun süre yaşarsa izlenen nesneler birikir, bellek artar ve veritabanındaki güncel değerlerle uyuşmayan eski veri görülebilir. Ayrıca bağlam iş parçacığı güvenli değildir. Web uygulamasında istek başına bir örnek kullanmak ve işi bitince bırakmak önerilen varsayılandır.

Kod önce mi, veritabanı önce mi yaklaşımı hangisi?

Kod önce yaklaşımında modeli C# sınıflarıyla yazar, şemayı migration ile üretirsiniz. Veritabanı önce yaklaşımında ise var olan bir veritabanından model sınıfları üretilir; EF Core bunun için tersine mühendislik (scaffolding) desteği sunar. Sıfırdan başlayan projelerde kod önce, mevcut kurumsal veritabanı üzerinde çalışırken veritabanı önce daha uygundur.

EF Core öğrenmek için hangi sırayla ilerlenmeli?

Önce C# ve LINQ temelleri, ardından tek tablolu basit bir CRUD uygulaması, sonra ilişkiler ve Include kullanımı gelir. Daha sonra migration'lar, DI ile ASP.NET Core entegrasyonu ve izleme davranışı öğrenilir. İleri aşamada performans, eş zamanlılık denetimi ve test stratejisi çalışılır. Resmî Microsoft öğreticileri her aşamada güvenilir kaynaktır.

Entity Framework Core ile Veri Erişimi: Adım Adım