EFEF Core Handbook

UZMAN

Diagnostics & Profiling

Production'da sorguların ne kadar sürdüğünü, kaç sorgu çalıştığını ve N+1 problemlerini tespit etmek için EF Core diagnostics araçlarını kullan.

1. EF Core Logging Levels

// Program.cs — Development'ta SQL göster
builder.Services.AddDbContext<AppDbContext>(options =>
    options.UseSqlServer(connectionString)
           .LogTo(Console.WriteLine, LogLevel.Information)  // Sadece dev!
           .EnableSensitiveDataLogging()                     // Parametre değerlerini göster
           .EnableDetailedErrors());                         // Detaylı hata mesajları

// Production: sadece warning ve üstü
// appsettings.Production.json
// "Logging": { "LogLevel": { "Microsoft.EntityFrameworkCore": "Warning" } }

2. Query Tags — SQL'de İzlenebilirlik

var products = await context.Products
    .TagWith("GetActiveProducts - ProductController.Index")
    .TagWith($"UserId: {currentUser.Id}")
    .Where(p => p.IsActive)
    .ToListAsync();
-- SQL Server Profiler / pg_stat_activity'de görürsün:
-- GetActiveProducts - ProductController.Index
-- UserId: 42

SELECT [p].[Id], [p].[Name], [p].[Price]
FROM [Products] AS [p]
WHERE [p].[IsActive] = 1;

3. ToQueryString() — Debug Aracı

// Query'yi çalıştırmadan SQL'ini gör (debug sırasında)
var query = context.Products
    .Where(p => p.Price > 100)
    .OrderBy(p => p.Name);

Console.WriteLine(query.ToQueryString());
// Parametre değerleriyle birlikte tam SQL çıktısı verir

4. OpenTelemetry Entegrasyonu

// NuGet: OpenTelemetry.Instrumentation.EntityFrameworkCore
builder.Services.AddOpenTelemetry()
    .WithTracing(tracing => tracing
        .AddEntityFrameworkCoreInstrumentation(options =>
        {
            options.SetDbStatementForText = true;  // SQL'i span'a ekle
            options.SetDbStatementForStoredProcedure = true;
        })
        .AddSource("Microsoft.EntityFrameworkCore")
        .AddConsoleExporter());  // veya Jaeger, Zipkin, OTLP

5. N+1 Tespit — Interceptor ile

// N+1 tespiti için EF Core Interceptor (production-safe, derlenebilir)
public class QueryCountInterceptor : DbCommandInterceptor
{
    private static readonly AsyncLocal<int> _queryCount = new();

    public static int CurrentRequestQueryCount => _queryCount.Value;
    public static void Reset() => _queryCount.Value = 0;

    public override InterceptionResult<DbDataReader> ReaderExecuting(
        DbCommand command, CommandEventData eventData, InterceptionResult<DbDataReader> result)
    {
        _queryCount.Value++;
        return result;
    }

    public override ValueTask<InterceptionResult<DbDataReader>> ReaderExecutingAsync(
        DbCommand command, CommandEventData eventData, InterceptionResult<DbDataReader> result,
        CancellationToken cancellationToken = default)
    {
        _queryCount.Value++;
        return ValueTask.FromResult(result);
    }
}

// Kayıt:
builder.Services.AddDbContext<AppDbContext>(options =>
    options.UseSqlServer(connectionString)
           .AddInterceptors(new QueryCountInterceptor()));

// Middleware ile tespit:
app.Use(async (context, next) =>
{
    QueryCountInterceptor.Reset();
    await next();
    
    var count = QueryCountInterceptor.CurrentRequestQueryCount;
    if (count > 10)
        Log.Warning("⚠️ N+1 şüphesi: {Path} → {Count} sorgu",
            context.Request.Path, count);

    // Header olarak da eklenebilir (dev ortamda)
    context.Response.Headers["X-EF-Query-Count"] = count.ToString();
});

Diagnostics Araçları Özet

Araç Ne Yapar Ortam
LogTo() EF loglarını konsola yaz Development
TagWith() SQL'e yorum ekle (izlenebilirlik) Her ortam
ToQueryString() SQL'i çalıştırmadan gör Debug
OpenTelemetry Distributed tracing, span'lar Production
MiniProfiler Web UI'da sorgu sürelerini göster Development/Staging
dotnet-counters EF runtime metrics Production