İlk EF Core uygulamanızı yazmanın en kısa yolu, resmî başlangıç öğreticisinin yaptığını yapmaktır: bir .NET console uygulaması oluşturur, SQLite sağlayıcısını eklersiniz, iki entity sınıfı ve bir DbContext yazarsınız, dotnet ef migrations add ve dotnet ef database update komutlarıyla veritabanını oluşturur, son olarak ekleme, okuma, güncelleme ve silme (CRUD) işlemlerini çalıştırırsınız. Tüm süreç birkaç dosya ve birkaç komuttan ibarettir.
Bu yazı, Entity Framework Core (EF Core) başlangıç öğreticisini adım adım uyguluyor ve her adımda arka planda ne olduğunu açıklıyor: EF Core hangi sınıfı tablo yapar, birincil anahtarı nereden anlar, ilişkiyi nasıl kurar, migration dosyaları neden oluşur ve SaveChangesAsync çağrısı neyi veritabanına yazar. Öğreticide SQLite kullanılır, çünkü belgelerin ifadesiyle .NET'in desteklediği tüm platformlarda çalışır ve ayrı bir veritabanı sunucusu kurmanızı gerektirmez.
Ön koşullar
.NET CLI ile ilerleyecekseniz yalnızca .NET SDK'sı yeterlidir; komutlar Windows, macOS ve Linux üzerinde aynıdır. Visual Studio ile ilerleyecekseniz belgeler Visual Studio 2022'nin 17.4 veya üstü bir sürümünü ve .NET desktop development iş yükünü ister. Güncel sürüm EF Core 10'u kullanacaksanız .NET 10 SDK'sı gerektiğini unutmayın; paketlerin ayrıntıları için EF Core kurulumu ve NuGet paketleri yazısına bakabilirsiniz.
1. Projeyi oluşturun ve SQLite sağlayıcısını ekleyin
Önce boş bir console projesi oluşturup klasörüne geçin, ardından SQLite sağlayıcı paketini ekleyin. Visual Studio kullanıyorsanız New project ile C# etiketli Console App şablonunu seçip projeye EFGetStarted adını verin; paketi Package Manager Console'da Install-Package Microsoft.EntityFrameworkCore.Sqlite ile ya da projeye sağ tıklayıp Manage NuGet Packages ile kurabilirsiniz.
dotnet new console -o EFGetStarted
cd EFGetStarted
dotnet add package Microsoft.EntityFrameworkCore.SqliteEF Core'u kurmak, aslında hedeflediğiniz veritabanının sağlayıcı paketini kurmak demektir. SQLite paketi, EF Core'un çekirdek paketlerini de bağımlılık olarak getirir. Başka bir veritabanı kullanacaksanız tek fark bu satırdaki paket adı ve birazdan göreceğiniz UseSqlite çağrısı olur; modelin ve CRUD kodunun geri kalanı aynı kalır.
2. Modeli yazın

Proje klasöründe Model.cs adında bir dosya oluşturup aşağıdaki kodu yazın. Bu, resmî öğreticideki modelin kendisidir:
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
public class BloggingContext : DbContext
{
public DbSet<Blog> Blogs { get; set; }
public DbSet<Post> Posts { get; set; }
public string DbPath { get; }
public BloggingContext()
{
var folder = Environment.SpecialFolder.LocalApplicationData;
var path = Environment.GetFolderPath(folder);
DbPath = System.IO.Path.Join(path, "blogging.db");
}
// The following configures EF to create a Sqlite database file in the
// special "local" folder for your platform.
protected override void OnConfiguring(DbContextOptionsBuilder options)
=> options.UseSqlite($"Data Source={DbPath}");
}
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; }
}Bu kodda EF Core neyi nereden anlıyor?
Kodun hiçbir yerinde "bu bir tablodur" ya da "bu bir birincil anahtardır" yazmıyor; EF Core bunları konvansiyonlarla, yani adlandırma ve tip kurallarıyla çıkarır. Bu kuralları bilmek, ileride kendi modelinizi yazarken neyi açıkça yapılandırmanız gerektiğini anlamanızı sağlar:
- Entity türleri: Context üzerinde
DbSetözelliği olarak sunulan türler modele entity olarak dahil edilir.BlogvePostbu yüzden modeldedir. - Tablo adları: İlişkisel veritabanlarında her entity, kendisini sunan
DbSetözelliğiyle aynı adı taşıyan bir tabloya eşlenir. Bu modelde tablolarBlogsvePostsolur. SQLite şema kavramını desteklemediği için tablolar bir şemaya yerleştirilmez. - Birincil anahtar:
Idya da<tür adı>Idadlı özellik birincil anahtar olarak yapılandırılır.BlogIdvePostIdbu kurala uyar. Bileşik olmayan sayısal anahtarlar için EF Core, eklenen satırlarda değerin veritabanı tarafından üretilmesini de konvansiyonla ayarlar; bu yüzden yeni bir blog eklerkenBlogIdvermezsiniz. - İlişki:
Blog.Postskoleksiyon navigation'ı,Post.Blogise referans navigation'ıdır.Post.BlogIdözelliği bu ilişkinin yabancı anahtarı olarak bulunur. Böylece bir blogun birden çok yazısı olan bire çok ilişki kurulur. - Zorunlu ilişki ve cascade delete:
BlogIdnullable olmayan birintolduğu için ilişki zorunlu kabul edilir. Belgelere göre zorunlu ilişkiler varsayılan olarak cascade delete kullanır; yani bir blog silindiğinde ona bağlı yazılar da silinir. Bunu birazdan CRUD kodunda göreceksiniz.
Bu kuralların her biri ayrı bir yazının konusu: anahtarlar için primary key ve composite key, ilişkiler için ilişkiler: navigation ve foreign key ile one-to-many ilişki, silme davranışı için cascade delete yazılarına bakabilirsiniz.
Veritabanı dosyası nerede oluşuyor?
Context'in kurucusu, platformunuzun "yerel uygulama verisi" klasörünü (Environment.SpecialFolder.LocalApplicationData) bulup içine blogging.db dosya yolunu hazırlar. OnConfiguring metodu da bu yolu Data Source= ile SQLite bağlantı dizesine koyar. Bu sayede dosya, programı hangi klasörden çalıştırdığınızdan bağımsız olarak hep aynı yerde oluşur. Bu klasörün tam yeri işletim sistemine göre değişir; birazdan çalıştıracağınız program, yolu ekrana yazdırarak nerede olduğunu size söyler.
3. Veritabanını migration ile oluşturun
Model hazır; şimdi bu modele uyan veritabanını oluşturacaksınız. Öğretici bunu migration'larla yapar. Komut satırında sırasıyla şunları çalıştırın:
dotnet tool install --global dotnet-ef
dotnet add package Microsoft.EntityFrameworkCore.Design
dotnet ef migrations add InitialCreate
dotnet ef database updateİlk iki satır araçları hazırlar: dotnet ef aracını global olarak kurar ve projede komut çalıştırabilmek için gereken Design paketini ekler. Araç bir kez kurulduktan sonra sonraki projelerde ilk satırı tekrarlamanız gerekmez. Üçüncü satır, model için başlangıç tablolarını oluşturacak migration'ı iskelet olarak üretir. Dördüncü satır veritabanını oluşturur ve bu migration'ı ona uygular.
Visual Studio'da aynı işi Package Manager Console'dan yapabilirsiniz. Orada dotnet-ef yerine Tools paketi kurulur ve komutlar fiille başlar:
Install-Package Microsoft.EntityFrameworkCore.Tools
Add-Migration InitialCreate
Update-DatabaseMigration komutu ne üretti?
migrations add komutu projenizde Migrations adlı bir klasör oluşturur ve içine birkaç dosya üretir. Belgeler, EF Core'un ne ürettiğini incelemenin ve gerekirse düzeltmenin iyi bir fikir olduğunu söyler. Bu dosyalardan biri model snapshot dosyasıdır: modelin o anki hâlinin bir fotoğrafı. Bir sonraki migration'ı eklediğinizde EF Core güncel modelinizi bu snapshot ile karşılaştırır ve yalnızca farkı yeni migration'a yazar. Bu yüzden Migrations klasörü kaynak kontrolüne eklenir.
database update komutu ise veritabanı yoksa oluşturur, tabloları kurar ve hangi migration'ın uygulandığını veritabanındaki özel bir migration geçmişi tablosuna kaydeder. Bu kayıt sayesinde aynı komutu tekrar çalıştırdığınızda EF Core yalnızca henüz uygulanmamış migration'ları uygular. Migration komutlarının ayrıntıları ve günlük iş akışı EF Core migration rehberi yazısında.
Komutlar hata verirse ilk bakmanız gereken yer, komutu proje klasöründe çalıştırıp çalıştırmadığınızdır. Araçlar varsayılan olarak bulunduğunuz klasördeki projeyi hem hedef hem başlangıç projesi kabul eder. Proje başka bir klasördeyse --project ve --startup-project seçenekleri gerekir; bu konular dotnet ef komutları yazısında anlatılıyor.
4. CRUD işlemlerini yazın

Program.cs dosyasının içeriğini aşağıdaki kodla değiştirin. Kod, öğreticideki CRUD örneğinin aynısıdır:
using System;
using System.Linq;
using Microsoft.EntityFrameworkCore;
using var db = new BloggingContext();
// Note: This sample requires the database to be created before running.
Console.WriteLine($"Database path: {db.DbPath}.");
// Create
Console.WriteLine("Inserting a new blog");
db.Add(new Blog { Url = "http://blogs.msdn.com/adonet" });
await db.SaveChangesAsync();
// Read
Console.WriteLine("Querying for a blog");
var blog = await db.Blogs
.OrderBy(b => b.BlogId)
.FirstAsync();
// Update
Console.WriteLine("Updating the blog and adding a post");
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();
// Delete
Console.WriteLine("Delete the blog");
db.Remove(blog);
await db.SaveChangesAsync();Kodu adım adım okuyalım, çünkü EF Core'un temel çalışma biçimi bu birkaç satırda saklı:
- Context'i oluşturma:
using var db = new BloggingContext();satırı bir context örneği oluşturur veusingsayesinde program bitince onu dispose eder. Context, veritabanıyla bir oturumu temsil eder ve kısa ömürlü olacak şekilde tasarlanmıştır; bu konunun ayrıntısı DbContext yaşam döngüsü yazısında. - Create:
db.Add(...)yeni blogu context'e "eklenecek" olarak işaretler; bu noktada veritabanına hiçbir şey yazılmaz.SaveChangesAsyncçağrıldığında EF Core izlediği değişiklikleri bulur ve ekleme komutunu çalıştırır. Veritabanının ürettiğiBlogIddeğeri de nesneye geri yazılır. - Read:
db.Blogs.OrderBy(b => b.BlogId).FirstAsync()bir LINQ sorgusudur; EF Core bunu SQL'e çevirip ilk blogu getirir. Sorgudan dönen nesne context tarafından izlenir, yani context onun üzerindeki değişiklikleri fark edebilir. - Update: İzlenen blogun
Urldeğerini değiştirmek vePostskoleksiyonuna yeni bir yazı eklemek için ayrı bir "güncelle" metodu çağırmanız gerekmez. Bir sonrakiSaveChangesAsyncçağrısında EF Core değişen kolonu günceller, yeni yazıyı ekler ve yazınınBlogIdyabancı anahtarını ilişkiden doldurur. - Delete:
db.Remove(blog)blogu silinecek olarak işaretler;SaveChangesAsyncsilmeyi uygular. İlişki zorunlu olduğu için blogla birlikte ona bağlı "Hello World" yazısı da cascade delete ile silinir.
Koddaki her await önemlidir: EF Core aynı context üzerinde paralel işlemleri desteklemez ve async çağrıları hemen beklemeniz gerekir. Bu kuralın neden var olduğunu ve ihlal edilince ne olduğunu async ve second operation hatası yazısı anlatıyor. Ekleme, güncelleme ve silmenin daha ayrıntılı hâli ise SaveChanges: ekleme, güncelleme, silme yazısında.
5. Uygulamayı çalıştırın
Komut satırından dotnet run ile ya da Visual Studio'da Debug > Start Without Debugging ile çalıştırın. Program önce veritabanı dosyasının yolunu, ardından her adımda koddaki Console.WriteLine mesajlarını sırayla yazar. Kodun başındaki not önemlidir: bu örnek, veritabanının önceden oluşturulmuş olmasını bekler. Üçüncü adımdaki database update komutunu atladıysanız tablolar olmadığı için ilk SaveChangesAsync çağrısında hata alırsınız.
dotnet runProgram sonunda blogu sildiği için, programı tekrar tekrar çalıştırdığınızda veritabanı her seferinde aynı duruma döner. Veriyi görmek isterseniz silme adımını geçici olarak yorum satırına alıp yazdırılan yoldaki blogging.db dosyasını bir SQLite aracıyla açabilirsiniz.
6. Modeli değiştirin: ikinci migration
Gerçek projede model sürekli değişir. Migration belgelerindeki senaryoyu kendi modelimize uygulayalım: bloglara bir oluşturulma zamanı eklemek istiyorsunuz. Blog sınıfına yeni bir özellik ekleyin:
public class Blog
{
public int BlogId { get; set; }
public string Url { get; set; }
public DateTime CreatedTimestamp { get; set; }
public List<Post> Posts { get; } = new();
}Artık model ile veritabanı uyumsuzdur: tabloda bu kolon yok. Yeni bir migration ekleyip uygulayın. Belgeler migration'lara açıklayıcı adlar vermeyi önerir, böylece proje geçmişi sonradan kolayca okunur.
dotnet ef migrations add AddBlogCreatedTimestamp
dotnet ef database updateBu kez EF Core, güncel modeli bir önceki migration'da kaydedilen snapshot ile karşılaştırır, bir kolonun eklendiğini tespit eder ve yalnızca bu değişikliği içeren bir migration üretir. database update çalıştığında veritabanının zaten var olduğunu görür ve migration geçmişi tablosundaki kayıt sayesinde yalnızca yeni migration'ı uygular.
SQLite'ın migration sınırlamaları
SQLite, diğer ilişkisel veritabanlarının çoğunun desteklediği bazı şema işlemlerini desteklemez. Belgelere göre kolon eklemek, tablo veya indeks oluşturmak, kolon ve tablo yeniden adlandırmak doğrudan desteklenir; kolon silmek, kolon tipini değiştirmek, yabancı anahtar ya da birincil anahtar eklemek gibi işlemler ise tablonun yeniden oluşturulmasıyla (rebuild) yapılır. Rebuild yalnızca EF Core modelinizin parçası olan veritabanı nesneleri için mümkündür; modelde olmayan bir nesne, örneğin bir migration içinde elle oluşturulmuş bir yapı söz konusuysa NotSupportedException fırlatılır. SQLite ayrıca şema ve sequence kavramlarını desteklemez; DateTimeOffset, TimeSpan ve ulong tiplerinde de eşitlik dışındaki karşılaştırma ve sıralamalar SQL'e çevrilmez. Öğrenme için ideal olan SQLite'ın üretimde size uyup uymadığını değerlendirmek için veritabanı sağlayıcıları yazısına bakın.
Migration yerine EnsureCreated kullanılabilir mi?
EF Core'da şemayı oluşturmanın daha hafif bir yolu da vardır: EnsureCreatedAsync. Bu metot veritabanı yoksa oluşturur ve şemayı kurar; ancak veritabanında herhangi bir tablo varsa (başka bir context'in tabloları dahil) şemaya dokunmaz. Belgeler bu yöntemi prototip, test veya yerel önbellek gibi, şema değiştiğinde verinin silinebildiği senaryolar için önerir. İki önemli uyarı vardır: EnsureCreatedAsync ile migration'lar birlikte iyi çalışmaz, migration kullanıyorsanız şemayı bununla oluşturmamalısınız; ve EnsureCreatedAsync'den migration'a geçmek pürüzsüz değildir, en basit yol veritabanını silip migration ile yeniden oluşturmaktır. İleride migration kullanmayı düşünüyorsanız öğreticinin yaptığı gibi baştan migration ile başlamak en doğrusudur.
Sık yapılan hatalar

- Design paketini eklemeyi unutmak:
dotnet efkurulu olsa bile projeyeMicrosoft.EntityFrameworkCore.Designeklenmemişse migration komutları çalışmaz. - Araç ve paket sürümlerini karıştırmak:
dotnet-efaracının ana sürümü, projedeki EF Core paketlerinin ana sürümüyle aynı olmalıdır. - Veritabanını oluşturmadan programı çalıştırmak: CRUD kodu tabloların var olduğunu varsayar; önce
database updateçalıştırın. - Model değişikliğinden sonra migration eklememek: Yeni bir özellik eklediğinizde yeni bir migration eklemezseniz veritabanı eski şemada kalır. Bekleyen model değişikliklerini yakalamanın yolları pending model changes hatası yazısında.
- Yanlış veritabanı dosyasına bakmak: Dosya, program klasöründe değil, yerel uygulama verisi klasöründe oluşur. Program tarafından yazdırılan yolu kullanın.
- EnsureCreated ile migration'ı karıştırmak: Aynı veritabanında ikisini birlikte kullanmayın.
Sonraki adımlar
Bu öğretici bilinçli olarak basit tutuldu. Belgelerin önerdiği sonraki adımlar şunlardır: modelinizi yapılandırarak zorunlu alanlar ve maksimum uzunluk gibi kuralları belirlemek (model yapılandırma ve property yapılandırma), LINQ sorgularını öğrenmek (EF Core LINQ sorguları) ve şema değişikliklerini migration'larla yönetmek. EF Core'u bir web uygulamasında kullanmak ASP.NET Core tarafında ayrı bir konudur; context'in orada nasıl kaydedildiğini DbContext yapılandırma yazısında EF belgelerinin gösterdiği kadarıyla anlatıyoruz. Aynı modeli WinForms veya WPF masaüstü uygulamasında kullanmak için de resmî belgelerde ayrı başlangıç öğreticileri bulunur.
Sık Sorulan Sorular
Neden öğreticide SQLite kullanılıyor?
Belgeler SQLite'ı, .NET'in desteklediği tüm platformlarda çalıştığı için seçer. Ayrı bir sunucu kurmanız gerekmez; veritabanı tek bir dosyadır. Aynı kodu SQL Server veya PostgreSQL ile çalıştırmak için sağlayıcı paketini ve UseSqlite çağrısını değiştirmeniz yeterlidir.
BlogId alanına neden değer vermiyoruz?
Çünkü BlogId konvansiyonla birincil anahtar olarak bulunur ve bileşik olmayan sayısal anahtarlar için değer üretimi otomatik ayarlanır. Değer, ekleme sırasında veritabanı tarafından üretilir ve SaveChangesAsync sonrasında nesneye geri yazılır.
Update adımında neden ayrı bir güncelleme metodu çağrılmıyor?
Blog nesnesi aynı context ile sorgulandığı için context tarafından izlenir. İzlenen nesnelerdeki değişiklikler SaveChangesAsync çağrısında otomatik tespit edilir. Bu davranışın ayrıntısı change tracker ve EntityState yazısında.
Blogu silince yazılar neden de siliniyor?
Post.BlogId nullable olmadığı için ilişki zorunludur ve zorunlu ilişkiler varsayılan olarak cascade delete kullanır. Yazıların blog silinince kalmasını istiyorsanız yabancı anahtarı nullable yaparak ilişkiyi isteğe bağlı hâle getirebilir ya da silme davranışını açıkça yapılandırabilirsiniz.
Marka adları sahiplerine aittir. Bu yazı bağımsız bir teknik rehberdir; adı geçen kuruluşlarla bayilik veya ortaklık ilişkisi bulunduğu anlamına gelmez.
İlgili içerikler: EF Core öğrenme rehberi · EF Core kurulumu · EF Core migration rehberi · DbContext yaşam döngüsü