EF Core (Entity Framework Core) kullanan bir uygulamayı test ederken resmî belgelerin önerisi nettir: testlerinizin önemli bir kısmını üretimde kullandığınız veritabanı sistemine karşı yazın; test double gerekiyorsa EF Core'un üstüne bir repository katmanı koyup onu taklit edin; bu mümkün değilse SQLite in-memory kullanın. EF Core in-memory sağlayıcısını test için kullanmak ise açıkça önerilmez: transaction ve ham SQL desteklemez, gerçek veritabanınızdan farklı davranır ve yalnızca eski uygulamalar için destekleniyor.
Bu yazıda test stratejisi seçimini, gerçek veritabanıyla hızlı ve izole testler yazmanın tekniklerini (fixture, transaction ile geri alma, temizlik), repository deseniyle veritabanısız testi, SQLite in-memory kurulumunu ve in-memory sağlayıcının sınırlarını belgelerdeki xUnit örnekleri üzerinden anlatıyoruz. Örnekler xUnit kullanır, ancak benzer kavramlar NUnit gibi diğer test çatılarında da vardır. Serinin genel haritası için EF Core öğrenme rehberi sayfasına bakın.
Temel karar: gerçek veritabanı mı, test double mı?
EF Core uygulaması için test yazarken vermeniz gereken ilk karar, testlerin uygulamanız gibi üretim veritabanı sisteminizi mi kullanacağı, yoksa onun yerine geçen bir test double'a karşı mı çalışacağıdır. Gerçek bir dış kaynağa karşı test etmenin bilinen zorlukları vardır. Bazı kaynaklara test ortamı olmadığı veya hız sınırları yüzünden karşı test etmek pratik değildir. Mümkün olsa bile çok sayıda testi uzak bir servise karşı çalıştırmak yavaş olabilir. Ayrıca paralel çalışan testler aynı veriyi değiştirip birbirini bozabilir; test double ise her testi kendi bellek içi kaynağında doğal olarak izole eder.
Öte yandan bir test double'a karşı geçen test, programınızın gerçek kaynakla çalıştığını garanti etmez. Belgelerin örneği öğreticidir: bir veritabanı test double'ı büyük/küçük harfe duyarlı karşılaştırma yaparken üretim veritabanınız duyarsız karşılaştırma yapıyor olabilir. Bu tür farklar ancak testler gerçek üretim veritabanınıza karşı çalıştırıldığında ortaya çıkar. Bu yüzden gerçek veritabanı testleri her test stratejisinin önemli bir parçasıdır.
Gerçek veritabanıyla test sanıldığı kadar zor değil
Belgeler, geliştiricilerin veritabanı testlerinden yavaş, zor veya güvenilmez oldukları düşüncesiyle kaçındığını ve bunun çoğu zaman doğru olmadığını savunur. Günümüzde çoğu veritabanı geliştirici makinesine kolayca kurulabilir; Docker gibi konteyner teknolojileri bunu çok kolaylaştırır ve Testcontainers gibi kütüphaneler testlerdeki konteyner veritabanlarının yaşam döngüsünü otomatikleştirebilir. Dev Container gibi teknolojiler veritabanı dahil tüm geliştirme ortamını kurabilir.
Yerel bir veritabanına makul bir test verisiyle karşı test etmek genellikle çok hızlıdır: iletişim tamamen yereldir ve test verisi veritabanı tarafında çoğunlukla bellekte tutulur. Belgeler bunu kendi örnekleriyle destekler: EF Core'un kendisi yalnızca SQL Server'a karşı 30.000'den fazla test içerir; bu testler birkaç dakikada güvenilir şekilde tamamlanır, her commit'te CI'da çalışır ve geliştiriciler tarafından yerelde de sık sık çalıştırılır. Hız için bellek içi sahte bir veritabanına ihtiyaç olduğu inancı, belgelere göre neredeyse hiçbir zaman doğru değildir.
Bu, test double'ların gereksiz olduğu anlamına gelmez. Örneğin veritabanı hatasını simüle etmek gibi başka türlü test edilemeyen senaryolar için test double gerekir. Ancak strateji önerisi şudur: üretim veritabanınıza karşı iyi bir test kapsamıyla başlayın, gerekirse test double'lı testleri sonradan ekleyin.
Test yaklaşımlarının karşılaştırması

Belgeler EF Core uygulamaları için dört yaygın test double tekniğini ele alır: SQLite'ı in-memory modda sahte veritabanı olarak kullanmak, EF Core in-memory sağlayıcısını sahte veritabanı olarak kullanmak, DbContext ve DbSet'i mock veya stub ile taklit etmek, ve EF Core ile uygulama kodu arasına bir repository katmanı koyup onu taklit etmek. Belgelerdeki karşılaştırma tablosu hangi işlevin hangi yaklaşımla test edilebildiğini gösterir:
| Özellik | In-memory | SQLite in-memory | Mock DbContext | Repository | Gerçek veritabanı |
|---|---|---|---|---|---|
| Test double türü | Fake | Fake | Fake | Mock/stub | Gerçek, double yok |
| Ham SQL | Hayır | Duruma bağlı | Hayır | Evet | Evet |
| Transaction | Hayır (yok sayılır) | Evet | Evet | Evet | Evet |
| Sağlayıcıya özgü çeviriler | Hayır | Hayır | Hayır | Evet | Evet |
| Birebir sorgu davranışı | Duruma bağlı | Duruma bağlı | Duruma bağlı | Evet | Evet |
| LINQ uygulamanın her yerinde | Evet | Evet | Evet | Hayır (yalnızca repository içinde) | Evet |
Repository sütunundaki "Hayır" şu anlama gelir: test edilebilir tüm veritabanı LINQ sorgularının, taklit edilebilmeleri için IEnumerable döndüren repository metotlarının içine alınması gerekir.
Gerçek veritabanıyla test: kurulum

Çoğu veritabanı sistemi hem CI ortamlarına hem de geliştirici makinelerine kolayca kurulabilir ve büyük veritabanlarının çoğu için hazır Docker imajları vardır. SQL Server kullanıyorsanız LocalDB, neredeyse hiç kurulum gerektirmeden yerelde test çalıştırmanıza izin verir; ancak belgeler LocalDB'nin sınırlarını da sayar: SQL Server Developer Edition'ın desteklediği her şeyi desteklemez, yalnızca Windows'ta vardır ve servis ilk açılırken ilk test çalıştırmasında gecikmeye neden olabilir. Bu yüzden belgeler genellikle LocalDB yerine tam özellikli SQL Server Developer Edition kurmayı önerir.
Bulut veritabanı kullanıyorsanız hız ve maliyet için genellikle veritabanının yerel bir sürümüne karşı test etmek uygundur. Örneğin üretimde Azure SQL kullanıyorsanız yerelde kurulu bir SQL Server'a karşı test edebilirsiniz; ikisi birbirine çok benzer, yine de üretime çıkmadan önce testleri Azure SQL'in kendisinde de çalıştırmak akıllıcadır. Veritabanlarının kendisi hakkında bilgi için SQL Server nedir ve PostgreSQL nedir yazılarına bakabilirsiniz.
Test veritabanını bir kez oluşturmak: fixture
En basit durumda test paketinizde birden fazla test sınıfının paylaştığı tek bir veritabanı vardır ve bu veritabanının test çalıştırmasının ömrü boyunca tam olarak bir kez oluşturulup tohumlanması gerekir. xUnit'te bunu veritabanını temsil eden bir class fixture ile yaparsınız:
public class TestDatabaseFixture
{
private const string ConnectionString = @"Server=(localdb)\mssqllocaldb;Database=EFTestSample;Trusted_Connection=True;ConnectRetryCount=0";
private static readonly object _lock = new();
private static bool _databaseInitialized;
public TestDatabaseFixture()
{
lock (_lock)
{
if (!_databaseInitialized)
{
using (var context = CreateContext())
{
context.Database.EnsureDeleted();
context.Database.EnsureCreated();
context.AddRange(
new Blog { Name = "Blog1", Url = "http://blog1.com" },
new Blog { Name = "Blog2", Url = "http://blog2.com" });
context.SaveChanges();
}
_databaseInitialized = true;
}
}
}
public BloggingContext CreateContext()
=> new BloggingContext(
new DbContextOptionsBuilder<BloggingContext>()
.UseSqlServer(ConnectionString)
.Options);
}Fixture oluşturulduğunda önce EnsureDeleted ile önceki çalıştırmadan kalan veritabanını siler, sonra EnsureCreated ile güncel model yapılandırmanızla yeniden oluşturur ve testlerin kullanacağı verileri ekler. Kilit ve statik bayrağın nedeni şudur: fixture tek bir test sınıfında kullanılıyorsa xUnit onu tam bir kez oluşturur, ama aynı fixture'ı birden fazla sınıfta kullanmak yaygındır. xUnit'in collection fixture mekanizması bu durumda test sınıflarının paralel çalışmasını engeller; paralellik test performansı için önemli olduğundan belgeler basit bir kilit ve statik bayrakla oluşturmanın bir kez yapılmasını garanti eder. Test sınıfı fixture'ı IClassFixture<TestDatabaseFixture> uygulayarak yapıcıda alır ve her test Fixture.CreateContext() ile kendi bağlamını oluşturur.
Tohum verisini baştan düşünmeye değer; belgeler, yeni bir test için tohum verisini sonradan değiştirmenin mevcut testleri bozabileceği konusunda uyarır. EnsureCreated'ın migration'larla birlikte iyi çalışmadığını ve yalnızca veritabanında hiç tablo yokken şema oluşturduğunu da hatırlatalım; üretimde migration kullanıyorsanız bu API'yi test veritabanı dışında kullanmayın. Ayrıntılar EF Core migration rehberi yazısında.
Veri değiştiren testleri transaction ile izole etmek
Salt okuma yapan testler izolasyon açısından kolaydır: hiçbir şey değişmediği için birbirlerini etkileyemezler. Veri değiştiren testler ise birbirine karışabilir. Yaygın bir teknik, testi bir transaction içine almak ve test sonunda transaction'ı geri almaktır. Hiçbir şey commit edilmediği için diğer testler değişiklikleri görmez:
[Fact]
public async Task AddBlog()
{
using var context = Fixture.CreateContext();
context.Database.BeginTransaction();
var controller = new BloggingController(context);
await controller.AddBlog("Blog3", "http://blog3.com");
context.ChangeTracker.Clear();
var blog = await context.Blogs.SingleAsync(b => b.Name == "Blog3");
Assert.Equal("http://blog3.com", blog.Url);
}Bu testte transaction hiç commit edilmez; bağlam test sonunda dispose edildiğinde örtük olarak geri alınır. Güncellemeden sonra çağrılan ChangeTracker.Clear(), blogun gerçekten veritabanından yüklenmesini sağlar; aksi halde bağlam zaten izlediği nesneyi döndürebilir. İki ayrı bağlam da kullanılabilirdi, ancak o zaman ikisinin aynı transaction'ı kullandığından emin olmanız gerekirdi. Belgeler ayrıca transaction'ı fixture'ın CreateContext metodunda başlatmayı önerir; böylece testler zaten transaction içindeki bir bağlam alır ve transaction'ı unutmaktan kaynaklanan, ayıklaması zor karışmalar önlenir. Transaction API'leri için transaction yönetimi yazısına bakın.
Transaction'ı kendisi yöneten kod
Son ve en zor kategori, hem veriyi değiştiren hem de transaction'ı kendisi açıkça yöneten ürün kodudur. Veritabanları genellikle iç içe transaction desteklemediğinden yukarıdaki gibi transaction ile izolasyon yapılamaz; transaction'ı ürün kodunun kendisi kullanır. Bu testler için her testten sonra veritabanını başlangıç durumuna döndürmeniz ve bu testlerin birbirine karışmaması için paralelliği kapatmanız gerekir.
Belgelerdeki örnekte bu testler kendi ayrı veritabanını kullanan ikinci bir fixture'a sahiptir ve fixture'da tabloyu boşaltıp tohum verisini yeniden yazan bir Cleanup metodu bulunur. Test sınıfı IDisposable uygular ve Dispose içinde Fixture.Cleanup() çağırır. Fixture birden fazla sınıf arasında paylaşılacaksa, sınıfların paralel çalışmasını engellemek için class fixture yerine bir xUnit collection fixture olarak tanımlanır; xUnit collection fixture'ı yalnızca bir kez oluşturduğu için kilit gerekmez. Veri değiştiren birden fazla test sınıfınız varsa her birine kendi veritabanını veren farklı fixture'larla paralel çalışmayı sürdürebilirsiniz; belgeler çok sayıda test veritabanı oluşturmanın sorun olmadığını belirtir.
Daha hızlı oluşturma ve temizlik
EnsureDeleted ve EnsureCreated bazı veritabanlarında yavaş olabilir. Kod üzerinde çalışırken testleri tekrar tekrar çalıştırıyorsanız fixture'daki EnsureDeleted satırını geçici olarak devre dışı bırakıp aynı veritabanını yeniden kullanabilirsiniz; ama model değiştiğinde şema güncel kalmayacağı için bunu yalnızca geliştirme döngüsünde geçici olarak yapın. Temizlikte EF Core ile RemoveRange çağırmak tabloyu boşaltmanın en verimli yolu değildir; hız önemliyse ham SQL ile silme yapabilirsiniz:
DELETE FROM [Blogs];Belgeler ayrıca veritabanını verimli şekilde temizleyen Respawn paketini anar; temizlenecek tabloları belirtmenizi gerektirmediği için modele tablo eklendikçe temizlik kodunu güncellemeniz gerekmez.
Veritabanısız test: repository deseni
Üretim veritabanınızı kullanmadan test yazmaya karar verdiyseniz belgelerin önerdiği teknik repository desenidir. İlk adım EF Core LINQ sorgularınızı ayrı bir katmana çıkarmaktır:
public interface IBloggingRepository
{
Task<Blog> GetBlogByNameAsync(string name);
IAsyncEnumerable<Blog> GetAllBlogsAsync();
void AddBlog(Blog blog);
Task SaveChangesAsync();
}Üretim uygulaması bir EF Core bağlamını sarar ve sorguları onun üzerinde çalıştırır. Kritik nokta, metotların IQueryable değil IAsyncEnumerable veya IEnumerable döndürmesidir. IQueryable döndürmek sonucun üzerine yeni sorgu operatörlerinin eklenebileceği, yani EF Core'un yine çeviri yapmak zorunda kalacağı anlamına gelir; bu da repository'nin amacını boşa çıkarır. Uygulama kodu bağlam yerine repository'yi enjekte eder; test ise repository'yi Moq gibi bir kütüphaneyle taklit eder:
[Fact]
public async Task GetBlog()
{
// Arrange
var repositoryMock = new Mock<IBloggingRepository>();
repositoryMock
.Setup(r => r.GetBlogByNameAsync("Blog2"))
.Returns(Task.FromResult(new Blog { Name = "Blog2", Url = "http://blog2.com" }));
var controller = new BloggingControllerWithRepository(repositoryMock.Object);
// Act
var blog = await controller.GetBlog("Blog2");
// Assert
repositoryMock.Verify(r => r.GetBlogByNameAsync("Blog2"));
Assert.Equal("http://blog2.com", blog.Url);
}Belgeler farkı şöyle özetler: sahte sağlayıcı yaklaşımları sorgu girdilerini (tabloları) taklit eder ama sorgu operatörlerini yine bellekte çalıştırır; repository deseni ise sorgu çıktılarını doğrudan taklit etmenize izin verir ve EF Core'u test yığınından tamamen çıkarır. Bedeli de vardır: her test edilebilir sorguyu bir metoda sarmak ek bir mimari katman getirir, uygulama ve bakım maliyeti yüksektir ve repository'nin sunduğu sorguların kendisi için yine gerçek veritabanı testleri gerekir. Buna karşılık veri erişim kodunu tek yerde toplamak ve birden fazla veritabanını desteklerken sorguları sağlayıcıya göre ayarlamak gibi test dışı faydalar da sağlar.
SQLite in-memory ile test
SQLite, test paketinizde üretim veritabanınızın yerine EF Core sağlayıcısı olarak kolayca yapılandırılabilir. Belgeler bu durumda SQLite'ın in-memory veritabanı özelliğini kullanmayı önerir; testler arasında kolay izolasyon sağlar ve gerçek dosyalarla uğraşmayı gerektirmez. Bilmeniz gereken kritik ayrıntı şudur: in-memory SQLite'ta alt seviye bir bağlantı her açıldığında yeni bir veritabanı oluşturulur ve bağlantı kapandığında silinir. EF Core ise bağlantıları gerektikçe, her sorguda açıp kapatır; bu da veritabanının her seferinde sıfırlanması demektir. Çözüm, bağlantıyı EF Core'a vermeden önce kendiniz açmak ve ancak test bittiğinde kapatmaktır:
public SqliteInMemoryBloggingControllerTest()
{
// Baglanti acilinca in-memory veritabani olusur ve baglanti kapanana kadar yasar
_connection = new SqliteConnection("Filename=:memory:");
_connection.Open();
_contextOptions = new DbContextOptionsBuilder<BloggingContext>()
.UseSqlite(_connection)
.Options;
using var context = new BloggingContext(_contextOptions);
context.Database.EnsureCreated();
context.AddRange(
new Blog { Name = "Blog1", Url = "http://blog1.com" },
new Blog { Name = "Blog2", Url = "http://blog2.com" });
context.SaveChanges();
}
BloggingContext CreateContext() => new BloggingContext(_contextOptions);
public void Dispose() => _connection.Dispose();Kolay başlangıcına rağmen SQLite'ı sahte veritabanı olarak kullanmanın önemli bir sınırı vardır: EF Core'da farklı sağlayıcılar farklı davranır ve EF Core alttaki veritabanının her yönünü soyutlamaya çalışmaz. Belgelerin saydığı farklar şunlardır: aynı LINQ sorgusu farklı sağlayıcılarda farklı sonuç döndürebilir, örneğin SQL Server varsayılan olarak büyük/küçük harfe duyarsız, SQLite duyarlı karşılaştırma yapar; SQL Server'da çalışan bazı sorgular SQLite'ta desteklenmez; SQL Server'ın EF.Functions.DateDiffDay gibi sağlayıcıya özgü bir metodunu kullanan sorgu SQLite'ta başarısız olur ve test edilemez; ham SQL ise çalışabilir, başarısız olabilir veya farklı sonuç döndürebilir. SQLite sağlayıcısının modelleme sınırlamaları da vardır: şemaları, sequence'ları ve veritabanının ürettiği concurrency token'ları desteklemez. Belgelere göre bu sınırlar başta sorun gibi görünmese de zamanla genellikle sorun haline gelir. Sağlayıcı farklarının ayrıntısı için veritabanı sağlayıcıları yazısına bakın.
EF Core in-memory sağlayıcısı: neden önerilmiyor?

EF Core, aslen EF Core'un kendi iç testlerini desteklemek için tasarlanmış bir in-memory sağlayıcısıyla gelir. Bazı geliştiriciler onu uygulama testlerinde sahte veritabanı olarak kullanır; belgeler bunu kesinlikle önermez. Sağlayıcı sayfası da onun performans veya sağlamlık için tasarlanmadığını, test ortamları dışında kullanılmaması gerektiğini ve artık yeni özellik eklenmediğini belirtir. Sahte veritabanı olarak SQLite'ın tüm sorunlarını taşır ve bunlara ek olarak:
- İlişkisel bir veritabanı olmadığı için SQLite sağlayıcısından daha az sorgu türünü destekler; daha fazla sorgu başarısız olur veya üretimden farklı davranır.
- Transaction desteklemez. Varsayılan olarak bir transaction başlatıldığında hata fırlatır;
InMemoryEventId.TransactionIgnoredWarninguyarısını yok sayacak şekilde yapılandırırsanız transaction sessizce yok sayılır, ama geri alma gibi transaction anlamına dayanan kodunuz yine doğru test edilmez. - Ham SQL'i hiç desteklemez.
- Performans için optimize edilmemiştir ve genellikle in-memory moddaki SQLite'tan, hatta üretim veritabanınızdan daha yavaş çalışır.
Belgelere göre in-memory veritabanları basit bir adla tanımlanır ve aynı adı veren bağlamlar aynı veritabanına bağlanır; bu yüzden örnek kurulum her testten önce EnsureDeleted çağırır. Bu veritabanları bağlamın iç servis sağlayıcısına bağlıdır; farklı seçeneklerle yapılandırılan bağlamlar yeni bir iç servis sağlayıcısı kullanabileceğinden, veritabanını paylaşması gereken tüm bağlamlara aynı InMemoryDatabaseRoot örneğini, genellikle statik bir alan üzerinden vermeniz gerekir. Belgelerin sonucu açıktır: basit bir bellek içi sahte veritabanı arıyorsanız in-memory sağlayıcı yerine SQLite kullanın, ama önce repository desenini düşünün.
DbSet mock'lamak
DbContext'i mock'lamak, Add veya SaveChanges çağrılarının yapıldığını doğrulamak gibi sorgu dışı işlevler için iyi bir yaklaşım olabilir. Ancak DbSet'in sorgu işlevini düzgün şekilde mock'lamak mümkün değildir, çünkü sorgular IQueryable üzerindeki statik genişletme metotlarıyla ifade edilir. "DbSet mock'lamak" denince genellikle kastedilen, bellek içi bir koleksiyonla desteklenen bir DbSet oluşturup operatörleri bellekte çalıştırmaktır; bu da pratikte in-memory sağlayıcıyla aynı sorunları taşır. Belgeler sorgu kodunu test etmek için bu yöntemden kaçınmayı önerir.
Testte servis değiştirirken: EnableServiceProviderCaching
EF Core, sorgu derleme ve model oluşturma gibi işler için bir iç servis sağlayıcısı kullanır ve aynı yapılandırmayı paylaşan bağlamlar için bu sağlayıcıyı varsayılan olarak önbelleğe alır. EnableServiceProviderCaching(false) bu önbelleği kapatır. Belgeler kapatmanın bağlam oluşturmayı çok yavaşlattığını ve çoğu durumda varsayılanın önerildiğini söyler; tek istisna olarak, test amacıyla iç servisleri değiştiriyorsanız her testin taze bir servis sağlayıcısı alması için önbelleği kapatabileceğinizi belirtir.
Sık yapılan hatalar
- Hız için in-memory sağlayıcıya geçmek: Belgelere göre yerel gerçek veritabanı genellikle yeterince hızlıdır; in-memory sağlayıcı daha yavaş da olabilir.
- SQLite'ta geçen testi üretime güvence saymak: Büyük/küçük harf duyarlılığı, sağlayıcıya özgü fonksiyonlar ve ham SQL farklı davranabilir.
- SQLite in-memory bağlantısını EF'ye açmadan vermek: Her sorguda bağlantı kapanıp açılır ve veritabanı sıfırlanır.
- Repository'den IQueryable döndürmek: Sonuç yine EF Core çevirisi gerektirir ve taklit edilemez.
- Veri değiştiren testleri izolasyonsuz paralel çalıştırmak: Transaction ile geri alma, ayrı veritabanları veya temizlik gerekir.
- Transaction yöneten kodu transaction ile izole etmeye çalışmak: İç içe transaction genellikle desteklenmez; temizlik ve paralelliği kapatma gerekir.
- Tohum verisini rastgele değiştirmek: Mevcut testler bu veriye bağlı olabilir.
Sık Sorulan Sorular
UseInMemoryDatabase ile test yazmak yanlış mı?
Belgeler bunu açıkça önermez ve yalnızca eski uygulamalar için desteklendiğini söyler. Transaction ve ham SQL desteklemez, ilişkisel değildir ve üretim veritabanınızdan farklı davranır. Gerçek veritabanı, repository deseni veya en azından SQLite in-memory tercih edilmelidir.
Testler için LocalDB yeterli mi?
Neredeyse kurulumsuz çalıştığı için başlangıçta pratiktir. Ancak yalnızca Windows'ta vardır, Developer Edition'ın her özelliğini desteklemez ve ilk çalıştırmada gecikme yaratabilir; belgeler genellikle SQL Server Developer Edition'ı önerir.
Veri değiştiren testleri nasıl izole ederim?
En yaygın teknik, testi commit edilmeyen bir transaction içinde çalıştırmaktır; bağlam dispose edildiğinde değişiklikler geri alınır. Kodun kendisi transaction yönetiyorsa ayrı bir veritabanı, her testten sonra temizlik ve paralelliğin kapatılması gerekir.
Repository deseni şart mı?
Hayır. Belgeler önce gerçek veritabanına karşı test kapsamı önerir. Repository deseni, test double gerektiğinde güvenilir taklit sağlayan tek yaklaşım olarak önerilir; ama uygulama ve bakım maliyeti yüksektir.
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 · Veritabanı sağlayıcıları · Transaction yönetimi · Migration rehberi · DbContext yapılandırma · SQLite nedir