در این مطلب میخواهیم یک Interceptor ایجاد کنیم برای پیدا کردن کوئری هایی که بیشتر از 2 ثانیه طول میکشند. برای پیاده سازی باید یک کلاس ایجاد کنیم که از کلاس DbCommandInterceptor ارث بری کند. کلاس DbCommandInterceptor در EF Core 3 و یا ورژن های بالاتر قرار دارد و به طور مثال نمیتوانید در EF Core 2.1 از آن استفاده کنید. برای پیاده سازی یک کلاس به نام DatabaseLongQueryLogger ایجاد میکنیم که از DbCommandInterceptor ارث بری کرده:

public class DatabaseLongQueryLogger : DbCommandInterceptor
{
    private readonly ILogger _logger;
    public DatabaseLongQueryLogger(ILoggerFactory loggerFactory)
    {
        _logger = loggerFactory.CreateLogger(nameof(DatabaseLongQueryLogger));
    }
    public override ValueTask<DbDataReader> ReaderExecutedAsync(DbCommand command, CommandExecutedEventData eventData, DbDataReader result, CancellationToken cancellationToken = default)
    {
        if (eventData.Duration.TotalMilliseconds > 2)
        {
            LogLongQuery(command, eventData);
        }
        return base.ReaderExecutedAsync(command, eventData, result, cancellationToken);
    }

    private void LogLongQuery(DbCommand command, CommandExecutedEventData eventData)
    {
        _logger.LogWarning($"Long query:{command.CommandText}. TotalMilliseconds:{eventData.Duration.TotalMilliseconds}");
    }

    public override DbDataReader ReaderExecuted(DbCommand command, CommandExecutedEventData eventData, DbDataReader result)
    {
        if (eventData.Duration.TotalMilliseconds > 2000)
        {
            LogLongQuery(command, eventData);
        }
        return base.ReaderExecuted(command, eventData, result);
    }
}

کلاس DbCommandInterceptor در پکیج Microsoft.EntityFrameworkCore.Relational قرار دارد و برای استفاده باید این پکیج را نصب نمایید.

متد ReaderExecutedAsync زمانی اجرا میشود که دیتایی را از دیتابیس خوانده باشید. (اگر از متد های async استفاده کنید متد ReaderExecutedAsync اجرا میشود و اگر از متدهای sync استفاده کنید متد ReaderExecuted اجرا میشود)

مقدار پراپرتی Duration برابر است با مقدار زمانی که طول کشیده است تا کوئری اجرا شود و با استفاده از همین پراپرتی میتوانیم کوئری هایی که زمان آنها طولانی هست را پیدا کنیم. 

سپس باید هنگام رجیستر کردن دیتابیس, باید این Interceptor ایجاد شده را معرفی کنیم توسط متد AddInterceptors.

public class Startup
{
    readonly ILoggerFactory loggerFactory = LoggerFactory.Create(builder => { builder.AddConsole(); });

    public Startup(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public IConfiguration Configuration { get; set; }
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddControllers();
        services.AddDbContext<ApplicationDbContext>(options =>
        {
            options.UseSqlServer(Configuration.GetConnectionString(nameof(ApplicationDbContext)));
            options.AddInterceptors(new DatabaseLongQueryLogger(loggerFactory));// <-- NOTE THIS
        });
    }
    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        app.UseRouting();
        app.UseEndpoints(endpoints =>
        {
            endpoints.MapDefaultControllerRoute();
        });
    }
}

اکنون اگر دیتایی را از دیتابیس بخوانید و بیشتر از 2 ثانیه طول بکشد, در صفحه کنسول دستور آن و مقدار زمان سپری شده را مشاهده میکنید:

var tags = await _context.Tags.ToListAsync(cancellationToken);

صفحه کنسول:

warn: DatabaseLongQueryLogger[0]
      Long query:SELECT [t].[Id], [t].[TagTitle]
      FROM [Tags] AS [t]. TotalMilliseconds:112.9079

لاگ ایجاد شده برای دریافت لیست تگ ها از دیتابیس بوده و 112 میلی ثانیه طول کشیده است. (برای تست شرط لاگ زدن را بر روی 2 میلی ثانیه قرار دادم)

if (eventData.Duration.TotalMilliseconds > 2)

;)

Powered by Froala Editor