EF Core'da DbContext, tek bir iş birimi (unit of work) için tasarlanmış kısa ömürlü bir nesnedir: oluşturulur, sorgularla gelen veya eklenen entity'leri izler, SaveChanges ya da SaveChangesAsync ile değişiklikleri veritabanına yazar ve dispose edilir. Web uygulamalarında genellikle her HTTP isteği bir iş birimidir; bu yüzden context AddDbContext ile scoped olarak kaydedilir ve her istek kendi örneğini alır. Yapılandırma ise her durumda bir DbContextOptionsBuilder üzerinden yapılır; bu builder'a AddDbContext, OnConfiguring ya da doğrudan new ile ulaşabilirsiniz.
Bu yazı, Entity Framework Core (EF Core) belgelerinin context yaşam döngüsü, yapılandırma ve başlatma desenlerini anlatan sayfasını temel alıyor: context ne kadar yaşamalı, dependency injection ile nasıl kaydedilir, DI olmadan nasıl oluşturulur, ne zaman factory gerekir, sağlayıcı ve diğer seçenekler nasıl verilir, kalıtımda hangi kurucu imzası kullanılır ve EF Core 9 ile gelen ConfigureDbContext ne işe yarar. Context'in thread güvenliği ve async kullanım hataları ayrıntılı olarak async ve second operation hatası yazısında ele alınıyor.
DbContext yaşam döngüsü: tek bir iş birimi

Bir DbContext örneğinin ömrü oluşturulduğu anda başlar ve dispose edildiği anda biter. Belgelere göre örnek tek bir iş birimi için tasarlanmıştır, bu da ömrünün genellikle çok kısa olduğu anlamına gelir. Belgelerin alıntıladığı Martin Fowler tanımına göre iş birimi, bir iş işlemi sırasında veritabanını etkileyebilecek her şeyi takip eder ve iş bittiğinde veritabanını güncellemek için yapılması gerekenleri belirler. EF Core ile tipik bir iş birimi şu adımlardan oluşur:
- Bir
DbContextörneği oluşturulur. - Context entity örneklerini izlemeye başlar. Entity'ler ya bir sorgudan döndükleri için ya da context'e eklendikleri veya iliştirildikleri (attach) için izlenir hâle gelir.
- İş kuralının gerektirdiği değişiklikler izlenen entity'ler üzerinde yapılır.
SaveChangesveyaSaveChangesAsyncçağrılır; EF Core yapılan değişiklikleri tespit edip veritabanına yazar.- Context örneği dispose edilir.
Belgeler bu döngüyle ilgili üç önemli noktanın altını çizer. Birincisi, context'i kullandıktan sonra dispose etmek gerekir; böylece yönetilmeyen kaynaklar serbest kalır, olay ve kanca kayıtları kaldırılır ve örneğe referans kalması durumunda bellek sızıntısı önlenir. İkincisi, DbContext thread güvenli değildir: context'leri thread'ler arasında paylaşmamalı ve context'i kullanmaya devam etmeden önce tüm async çağrıları beklemelisiniz. Üçüncüsü, EF Core kodunun fırlattığı bir InvalidOperationException context'i kurtarılamaz bir duruma sokabilir; bu tür istisnalar bir program hatasına işaret eder ve kurtarılmak üzere tasarlanmamıştır. Pratikte bu, böyle bir hatadan sonra aynı context ile devam etmeye çalışmak yerine hatanın kaynağını düzeltmeniz gerektiği anlamına gelir.
Kısa ömür, performans kaygısı yaratmamalı. Belgelere göre EF Core, DbContext örneklerinin olabildiğince az ek yükle hızlıca oluşturulabileceği şekilde tasarlanmıştır; her işlem için yeni bir context oluşturmak genellikle sorun değildir. Context oluşturmak bir veritabanı işlemi de içermez. EF Core ayrıca bağlantıları genellikle her işlemden (örneğin bir sorgudan) hemen önce açar ve hemen ardından kapatır; bağlantıların yeniden kullanımını alt seviyedeki veritabanı sürücüsünün bağlantı havuzu yönetir. Yüksek performanslı senaryolarda context kurulum maliyeti önemli hâle gelirse DbContext pooling devreye girer.
Dependency injection ile kayıt (AddDbContext)
ASP.NET Core uygulamaları dependency injection ile yapılandırılır ve EF Core bu yapılandırmaya Program.cs içinde AddDbContext ile eklenir. Belgedeki örnek, bağlantı dizesini yapılandırmadan okur ve bulamazsa açık bir hata fırlatır:
var connectionString =
builder.Configuration.GetConnectionString("DefaultConnection")
?? throw new InvalidOperationException("Connection string"
+ "'DefaultConnection' not found.");
builder.Services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(connectionString));Bu kod, DbContext türeyen ApplicationDbContext sınıfını uygulamanın servis sağlayıcısına (dependency injection container) scoped bir servis olarak kaydeder, SQL Server sağlayıcısını kullanacak şekilde yapılandırır ve bağlantı dizesini ASP.NET Core yapılandırmasından okur. Bağlantı dizesinin nerede ve nasıl saklanacağı connection string yönetimi yazısının konusu.
AddDbContext ile verilen yapılandırmanın context'e ulaşabilmesi için sınıfın DbContextOptions<ApplicationDbContext> parametresi alan public bir kurucusu olmalı ve bu parametreyi temel sınıfa iletmelidir:
public class ApplicationDbContext : DbContext
{
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
: base(options)
{
}
}Context artık kurucu enjeksiyonuyla istediğiniz serviste kullanılabilir. Belgeler bunu bir controller üzerinden gösterir; ASP.NET Core tarafının ayrıntısı ayrı bir konu olduğu için aynı deseni sade bir servis sınıfıyla gösterelim:
public class BlogService
{
private readonly ApplicationDbContext _context;
public BlogService(ApplicationDbContext context)
{
_context = context;
}
}Sonuçta her istek için bir ApplicationDbContext örneği oluşturulur, bu isteğin iş birimini yürütmek için servislere verilir ve istek bittiğinde dispose edilir. Yani DI üzerinden aldığınız context'in ömrünü servis sağlayıcı yönetir; onu sizin ayrıca dispose etmeniz gerekmez. Birazdan göreceğiniz factory deseninde ise durum tam tersidir.
Scoped yaşam süresi neden güvenli?
Belgelere göre AddDbContext, context türlerini varsayılan olarak scoped yaşam süresiyle kaydeder. Çoğu ASP.NET Core uygulamasında bu, eşzamanlı erişim sorunlarına karşı güvenlidir, çünkü her istemci isteğini aynı anda yalnızca bir thread yürütür ve her istek ayrı bir DI kapsamı, dolayısıyla ayrı bir context örneği alır. Blazor Server barındırma modeli bunun bir istisnasıdır: orada bir kullanıcı devresini sürdürmek için tek bir mantıksal istek kullanılır ve varsayılan kapsamla devre başına yalnızca bir scoped context olur.
Kodunuz açıkça birden fazla thread'i paralel çalıştırıyorsa context örneklerine asla eşzamanlı erişilmediğinden emin olmanız gerekir. Belgeler bunun iki yolunu verir: context'i scoped kaydedip her thread için IServiceScopeFactory ile ayrı kapsam oluşturmak ya da AddDbContext'in ServiceLifetime parametresi alan aşırı yüklemesini kullanarak context'i transient kaydetmek. Bu kararın çok kiracılı uygulamalardaki karşılığı için global query filter ve multitenancy yazısına bakabilirsiniz; orada kiracı değişebiliyorsa transient yaşam süresinin neden gerektiği anlatılıyor.
DI olmadan: new ve OnConfiguring
Context'i DI kullanmadan doğrudan new ile de oluşturabilirsiniz. Yapılandırma bu durumda ya OnConfiguring metodunu ezerek ya da seçenekleri kurucuya geçirerek yapılır. En basit hâli şudur:
public class ApplicationDbContext : DbContext
{
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlServer(
@"Server=(localdb)\mssqllocaldb;Database=Test;ConnectRetryCount=0");
}
}Bu desen, bağlantı dizesi gibi yapılandırma bilgisini kurucu üzerinden vermeyi de kolaylaştırır. Belgedeki ikinci örnek, bağlantı dizesini bir alanda saklayıp OnConfiguring içinde kullanır:
public class ApplicationDbContext : DbContext
{
private readonly string _connectionString;
public ApplicationDbContext(string connectionString)
{
_connectionString = connectionString;
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlServer(_connectionString);
}
}Üçüncü yol, DI için yazılmış aynı context sınıfını açıkça oluşturmaktır. DbContextOptionsBuilder<TContext> ile bir DbContextOptions nesnesi üretip kurucuya verirsiniz. Böylece aynı sınıf hem web uygulamasında DI ile hem de bir konsol aracında, testte ya da arka plan işinde elle kullanılabilir:
var contextOptions = new DbContextOptionsBuilder<ApplicationDbContext>()
.UseSqlServer(@"Server=(localdb)\mssqllocaldb;Database=Test;ConnectRetryCount=0")
.Options;
using var context = new ApplicationDbContext(contextOptions);new ile oluşturduğunuz context'in sahibi sizsiniz; dispose etmek de sizin sorumluluğunuzdadır. Örneklerdeki using var bu yüzden vardır. Bu desenlerden hangisini seçtiğiniz, EF Core araçlarının context'i tasarım zamanında oluşturup oluşturamayacağını da etkiler; araçlar yalnızca sınırlı sayıda deseni tanır. Bu konu design-time DbContext factory yazısında ayrıntılı anlatılıyor.
DbContext factory: AddDbContextFactory
Bazı uygulama türleri dependency injection kullanır ama istenen context yaşam süresiyle örtüşen bir servis kapsamı oluşturmaz; belgeler buna örnek olarak ASP.NET Core Blazor'u verir. Böyle bir örtüşme olsa bile uygulamanın aynı kapsam içinde birden fazla iş birimi yürütmesi gerekebilir, örneğin tek bir HTTP isteği içinde birden çok bağımsız işlem. Bu durumlarda context yerine bir factory kaydedersiniz:
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContextFactory<ApplicationDbContext>(
options => options.UseSqlServer(
@"Server=(localdb)\mssqllocaldb;Database=Test;ConnectRetryCount=0"));
}Context sınıfı yine DbContextOptions<ApplicationDbContext> alan public bir kurucuya sahip olmalıdır; bu, AddDbContext ile aynı desendir. Factory, servislerinize IDbContextFactory<ApplicationDbContext> olarak enjekte edilir ve ihtiyaç duyduğunuz her iş birimi için CreateDbContext ile yeni bir context üretir:
public class BlogService
{
private readonly IDbContextFactory<ApplicationDbContext> _contextFactory;
public BlogService(IDbContextFactory<ApplicationDbContext> contextFactory)
{
_contextFactory = contextFactory;
}
public async Task DoSomething()
{
using (var context = _contextFactory.CreateDbContext())
{
// ...
}
}
}Factory deseninin asıl kazancı, context'in ömrünü servis kapsamından bağımsız olarak sizin belirlemenizdir: her iş birimi için yeni bir örnek oluşturur, işi bitince dispose edersiniz. Belgeler ayrıca, yazının sonunda anlatılan ConfigureDbContext gibi ileri seviye kompozisyon araçları yerine çoğu uygulamanın bu factory desenini kullanmasını önerir. Factory'nin havuzlanmış sürümü olan AddPooledDbContextFactory ve PooledDbContextFactory ise DbContext pooling yazısında anlatılıyor.
DbContextOptions: tüm yapılandırmanın başlangıç noktası

Tüm DbContext yapılandırmasının başlangıç noktası DbContextOptionsBuilder sınıfıdır. Belgeler bu builder'a ulaşmanın üç yolunu sayar: AddDbContext ve ilgili metotlar, OnConfiguring ve doğrudan new ile oluşturmak. Builder nereden gelirse gelsin aynı yapılandırma uygulanabilir. Ayrıca OnConfiguring, context nasıl oluşturulursa oluşturulsun her zaman çağrılır; yani AddDbContext kullanırken bile OnConfiguring içinde ek yapılandırma yapabilirsiniz.
Sağlayıcıyı yapılandırma
Her DbContext örneği bir ve yalnızca bir veritabanı sağlayıcısı kullanacak şekilde yapılandırılmalıdır. Aynı context türünün farklı örnekleri farklı sağlayıcılarla kullanılabilir, ama tek bir örnek yalnızca birini kullanır. Sağlayıcı, ona özgü bir Use* çağrısıyla seçilir. Bu metotlar sağlayıcının tanımladığı extension metotlardır, dolayısıyla önce sağlayıcının NuGet paketini kurmanız gerekir. Belgelerdeki tablo yaygın örnekleri şöyle verir:
| Veritabanı | Yapılandırma örneği | NuGet paketi |
|---|---|---|
| SQL Server veya Azure SQL | .UseSqlServer(connectionString) | Microsoft.EntityFrameworkCore.SqlServer |
| Azure Cosmos DB | .UseCosmos(connectionString, databaseName) | Microsoft.EntityFrameworkCore.Cosmos |
| SQLite | .UseSqlite(connectionString) | Microsoft.EntityFrameworkCore.Sqlite |
| EF Core in-memory veritabanı | .UseInMemoryDatabase(databaseName) | Microsoft.EntityFrameworkCore.InMemory |
| PostgreSQL (Microsoft dışı) | .UseNpgsql(connectionString) | Npgsql.EntityFrameworkCore.PostgreSQL |
| MySQL/MariaDB (Microsoft dışı) | .UseMySql(connectionString) | Pomelo.EntityFrameworkCore.MySql |
| Oracle (Microsoft dışı) | .UseOracle(connectionString) | Oracle.EntityFrameworkCore |
In-memory veritabanı için belgelerin uyarısı burada da tekrar edilir: üretim için tasarlanmamıştır ve test için bile en iyi seçim olmayabilir. Sağlayıcıya özgü isteğe bağlı ayarlar, Use* metoduna verilen ek bir builder ile yapılır. Belgedeki örnek, Azure SQL'e bağlanırken bağlantı dayanıklılığı için yeniden denemeyi açar:
public class ApplicationDbContext : DbContext
{
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder
.UseSqlServer(
@"Server=(localdb)\mssqllocaldb;Database=Test",
providerOptions => { providerOptions.EnableRetryOnFailure(); });
}
}SQL Server ve Azure SQL için aynı sağlayıcı kullanılır, ancak belgeler Azure SQL'e bağlanırken bağlantı dayanıklılığının kullanılmasını önerir. Yeniden denemenin transaction'larla birlikte nasıl davrandığı connection resiliency yazısında. Sağlayıcıların listesi ve kendine özgü ayarları için veritabanı sağlayıcıları yazısına bakın.
Diğer seçenekler
Diğer yapılandırma çağrıları Use* çağrısından önce ya da sonra zincirlenebilir; belgelere göre sıranın bir önemi yoktur. Sık kullanılan DbContextOptionsBuilder metotları şunlardır:
| Metot | Ne yapar |
|---|---|
| UseQueryTrackingBehavior | Sorgular için varsayılan izleme davranışını belirler |
| LogTo | EF Core loglarını almanın basit yolu |
| UseLoggerFactory | Microsoft.Extensions.Logging factory kaydeder |
| EnableSensitiveDataLogging | Uygulama verisini istisnalara ve loglara dahil eder |
| EnableDetailedErrors | Daha ayrıntılı sorgu hataları verir (performans pahasına) |
| ConfigureWarnings | Uyarıları ve diğer olayları yok sayar ya da hataya çevirir |
| AddInterceptors | EF Core interceptor'larını kaydeder |
| EnableServiceProviderCaching | İç servis sağlayıcının önbelleğe alınmasını kontrol eder |
| UseMemoryCache | EF Core'un kullandığı bellek önbelleğini yapılandırır |
| UseLazyLoadingProxies | Lazy loading için dinamik proxy kullanır |
| UseChangeTrackingProxies | Değişiklik takibi için dinamik proxy kullanır |
UseLazyLoadingProxies ve UseChangeTrackingProxies, Microsoft.EntityFrameworkCore.Proxies paketinden gelir; belgeler başka paketlerdeki EF Core eklentilerini bu tür .UseSomething() çağrılarıyla yapılandırmayı önerilen yol olarak gösterir. Loglama, hassas veri ve interceptor seçenekleri SQL loglama: LogTo ve interceptor yazısında, izleme davranışı ise AsNoTracking ve tracking sorguları yazısında anlatılıyor.
DbContextOptions ve DbContextOptions<TContext>
DbContextOptions alan context sınıflarının çoğu generic DbContextOptions<TContext> türünü kullanmalıdır. Belgelere göre bu, birden fazla context türü kayıtlıyken bile dependency injection'dan o context türüne ait doğru seçeneklerin çözülmesini sağlar. Kalıtım için tasarlanmamış sınıfları sealed yapmak da belgelerin önerdiği iyi bir uygulamadır, ama zorunlu değildir:
public sealed class SealedApplicationDbContext : DbContext
{
public SealedApplicationDbContext(DbContextOptions<SealedApplicationDbContext> contextOptions)
: base(contextOptions)
{
}
}Context türü kendisinden kalıtım alınacak şekilde tasarlandıysa durum değişir. Böyle bir temel sınıf, generic olmayan DbContextOptions alan protected bir kurucu sunmalıdır. Böylece farklı somut alt sınıflar kendi generic seçenek nesneleriyle aynı temel kurucuyu çağırabilir. DbContext sınıfının kendi kurucusunun generic olmayan DbContextOptions alması da tam olarak bu sebepledir:
public abstract class ApplicationDbContextBase : DbContext
{
protected ApplicationDbContextBase(DbContextOptions contextOptions)
: base(contextOptions)
{
}
}
public sealed class ApplicationDbContext1 : ApplicationDbContextBase
{
public ApplicationDbContext1(DbContextOptions<ApplicationDbContext1> contextOptions)
: base(contextOptions)
{
}
}Hem doğrudan oluşturulacak hem de kalıtım alınacak bir context sınıfı ise iki kurucuyu birlikte sunmalıdır: generic seçenek alan public bir kurucu ve generic olmayan seçenek alan protected bir kurucu.
ConfigureDbContext ile yapılandırmayı birleştirmek
EF Core 9.0'dan itibaren ConfigureDbContext ile bir context'e AddDbContext çağrısından önce ya da sonra ek yapılandırma uygulayabilirsiniz. Belgeler bunu ağırlıklı olarak yeniden kullanılabilir kütüphaneler ve bileşenler için orta seviye bir özellik olarak tanımlar; amaç, sağlayıcı yapılandırmasının tamamını değiştirmeden çakışmayan ayarlar eklemektir. Örneğin bir kütüphane, hassas veri loglamasını ve ayrıntılı hataları ekleyebilir:
var services = new ServiceCollection();
services.ConfigureDbContext<BlogContext>(options =>
options.EnableSensitiveDataLogging()
.EnableDetailedErrors());
services.AddDbContext<BlogContext>(options =>
options.UseInMemoryDatabase("BasicExample"));Sağlayıcıya özgü ayarları bağlantı dizesi vermeden de uygulayabilirsiniz; örneğin ConfigureDbContext içinde yalnızca UseSqlServer(sqlOptions => sqlOptions.EnableRetryOnFailure()) çağırıp bağlantı dizesini AddDbContext tarafına bırakmak mümkündür. Çağrı sırası kuralları şöyledir: hem ConfigureDbContext hem AddDbContext kullanıldığında veya bu metotlar birden çok kez çağrıldığında yapılandırma çağrılma sırasıyla uygulanır. Loglama, interceptor gibi çakışmayan seçenekler birleştirilir; çakışan seçeneklerde son çağrı kazanır.
Sık yapılan hatalar

- Context'i uygulama boyunca yaşatmak: Context tek bir iş birimi için tasarlanmıştır ve thread güvenli değildir; uzun ömürlü, paylaşılan bir örnek thread'ler arasında kullanıldığında eşzamanlı erişim hatalarına ve tanımsız davranışa yol açar. Her iş birimi için yeni bir context kullanın.
- Yaşam süresini karıştırmak: DI'dan gelen context'in ömrünü servis sağlayıcı yönetir; factory ya da
newile oluşturduğunuz context'in ömrünü ise siz yönetirsiniz. Hangi durumda olduğunuzu bilmek, dispose sorumluluğunu doğru yere koymanızı sağlar. - Factory'den gelen context'i dispose etmemek:
CreateDbContextile oluşturulan örnek container tarafından yönetilmez;usingşarttır. - Kurucuyu yanlış yazmak:
AddDbContextkullanıyorsanız context'inDbContextOptions<TContext>alan public bir kurucusu olmalıdır; aksi hâlde yapılandırma context'e ulaşmaz. - Use* metodunu bulamamak: Sağlayıcı paketi eksik ya da
using Microsoft.EntityFrameworkCore;satırı yoktur. - Await etmeden devam etmek: Bir async EF Core çağrısını beklemeden aynı context'i kullanmak context durumunu bozabilir; ayrıntılar async ve second operation hatası yazısında.
- InvalidOperationException sonrası aynı context ile devam etmek: Belgelere göre bu tür bir hata context'i kurtarılamaz bırakabilir.
Sık Sorulan Sorular
Bir web isteğinde kaç DbContext örneği olur?
AddDbContext ile kayıtlı bir context için istek başına bir örnek olur, çünkü kayıt scoped'dur ve her istek ayrı bir kapsam alır. Aynı istekte birden çok bağımsız iş birimi yürütmeniz gerekiyorsa AddDbContextFactory ile her biri için ayrı context oluşturabilirsiniz.
OnConfiguring ile AddDbContext birlikte kullanılabilir mi?
Evet. Belgelere göre OnConfiguring context nasıl oluşturulursa oluşturulsun her zaman çağrılır; bu yüzden AddDbContext ile temel yapılandırmayı verip OnConfiguring içinde ek ayarlar yapabilirsiniz.
Aynı context sınıfını farklı veritabanlarıyla kullanabilir miyim?
Farklı örnekler için evet: aynı context türünün bir örneği SQL Server, bir başka örneği SQLite kullanabilir. Tek bir örnek ise yalnızca bir sağlayıcı kullanabilir. Migration'ları birden çok sağlayıcıyla yönetmenin ayrıntıları migration özelleştirme yazısında.
Her istekte context oluşturmak yavaş değil mi?
Belgelere göre EF Core context'lerin az ek yükle oluşturulabilmesi için tasarlanmıştır ve her işlem için yeni context oluşturmak genellikle sorun değildir. Ölçümle bir sorun gördüğünüz yüksek performanslı senaryolarda context pooling kullanabilirsiniz.
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 · Connection string yönetimi · Async ve second operation hatası · DbContext pooling