EFEF Core Handbook

İLERİ

Global Exception Handling & Retry

Veritabanı hataları kaçınılmaz: deadlock, timeout, constraint violation, connection drop. Bu hataları doğru yakalamak (hangi exception = hangi hata), kullanıcıya anlamlı mesaj vermek ve geçici hatalar için retry mekanizması kurmak production stabilitesi için şart.

Veritabanı sağlayıcısı Bu sayfadaki eşleşen örnekleri seçilen sağlayıcıya göre gösterir.

EF Core Exception Tipleri

Exception Sebep Çözüm
DbUpdateException INSERT/UPDATE/DELETE hatası Inner exception'a bak
DbUpdateConcurrencyException Concurrency conflict (row version) Retry veya merge
RetryLimitExceededException Retry politikası tükendi Alert + fallback
SqlException (Number: 1205) Deadlock Otomatik retry
SqlException (Number: -2) Timeout Retry veya timeout artır
SqlException (Number: 2601/2627) Unique constraint violation Kullanıcıya bilgi ver

Constraint Violation Handling

try
{
    context.Products.Add(new Product { Sku = "EXISTING-SKU" });
    await context.SaveChangesAsync();
}
catch (DbUpdateException ex) when (ex.InnerException is SqlException sqlEx)
{
    switch (sqlEx.Number)
    {
        case 2601: // Unique index violation
        case 2627: // Unique constraint violation
            throw new BusinessException($"Bu SKU zaten mevcut: {sqlEx.Message}");

        case 547:  // FK constraint violation
            throw new BusinessException("İlişkili kayıt bulunamadı veya silinemez.");

        default:
            throw; // Bilinmeyen hata — yukarı fırlat
    }
}
-- SqlException 2601 tetikleyen SQL:
INSERT INTO [Products] ([Sku], [Name]) VALUES ('EXISTING-SKU', 'Test');
-- Msg 2601: Cannot insert duplicate key row in object 'Products' 
-- with unique index 'IX_Products_Sku'.
-- PostgresException 23505 tetikleyen SQL:
INSERT INTO products (sku, name) VALUES ('EXISTING-SKU', 'Test');
-- ERROR: duplicate key value violates unique constraint "ix_products_sku"
-- DETAIL: Key (sku)=(EXISTING-SKU) already exists.

Concurrency Exception Handling

async Task UpdateProductWithRetry(int productId, decimal newPrice, int maxRetries = 3)
{
    for (int attempt = 0; attempt < maxRetries; attempt++)
    {
        try
        {
            var product = await context.Products.FindAsync(productId);
            product!.Price = newPrice;
            await context.SaveChangesAsync();
            return; // Başarılı
        }
        catch (DbUpdateConcurrencyException ex)
        {
            var entry = ex.Entries.Single();
            
            // Strateji 1: "Database Wins" — DB'deki değeri al
            await entry.ReloadAsync();  // DB'den tekrar oku
            
            // Strateji 2: "Client Wins" — kendi değerini zorla yaz
            // entry.OriginalValues.SetValues(await entry.GetDatabaseValuesAsync());

            if (attempt == maxRetries - 1) throw;
        }
    }
}

Deadlock Retry

// Connection Resiliency ile otomatik deadlock retry
options.UseSqlServer(conn, o => o.EnableRetryOnFailure(
    maxRetryCount: 5,
    maxRetryDelay: TimeSpan.FromSeconds(30),
    errorNumbersToAdd: new[] { 1205 }));  // 1205 = Deadlock

// Manuel retry pattern (daha fazla kontrol)
public static class RetryHelper
{
    public static async Task<T> ExecuteWithRetryAsync<T>(
        Func<Task<T>> operation,
        int maxRetries = 3,
        int baseDelayMs = 100)
    {
        for (int i = 0; i < maxRetries; i++)
        {
            try { return await operation(); }
            catch (Exception ex) when (IsTransient(ex) && i < maxRetries - 1)
            {
                // Exponential backoff
                await Task.Delay(baseDelayMs * (int)Math.Pow(2, i));
            }
        }
        return await operation(); // Son deneme — hata fırlatabilir
    }

    private static bool IsTransient(Exception ex)
        => ex is DbUpdateException { InnerException: SqlException sql }
           && sql.Number is 1205 or -2 or 40613 or 40197;
}

Timeout Yönetimi

// Global command timeout (saniye)
options.UseSqlServer(conn, o => o.CommandTimeout(60));

// Tek sorgu için timeout
context.Database.SetCommandTimeout(TimeSpan.FromMinutes(5));

// Uzun çalışan sorgu — CancellationToken ile iptal
var cts = new CancellationTokenSource(TimeSpan.FromSeconds(30));
try
{
    var result = await context.Products
        .Where(p => p.IsActive)
        .ToListAsync(cts.Token);
}
catch (OperationCanceledException)
{
    // Sorgu timeout'a uğradı
    logger.LogWarning("Product query timed out after 30s");
}

Exception Türleri Karar Ağacı

DbUpdateException yakalandı
├── InnerException is SqlException?
│   ├── Number 2601/2627 → Unique violation → Kullanıcıya "zaten var" mesajı
│   ├── Number 547 → FK violation → "İlişkili kayıt problemi"
│   ├── Number 1205 → Deadlock → Retry (otomatik veya manuel)
│   ├── Number -2 → Timeout → Retry veya timeout artır
│   └── Diğer → Log + generic hata
├── Is DbUpdateConcurrencyException?
│   └── Concurrency conflict → Reload + retry veya merge UI
└── Diğer → Log + throw