首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >带有日志查询的EFCore监视器拦截器

带有日志查询的EFCore监视器拦截器
EN

Code Review用户
提问于 2022-02-03 10:07:47
回答 1查看 172关注 0票数 3

我创建了一个SqlMonitorInterceptor,它将检查查询和日志错误的执行时间。其主要思想是以一种可以轻松复制到并执行查询的方式记录查询。

SqlMonitorInterceptor:

代码语言:javascript
复制
using System;
using System.Data;
using System.Data.Common;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Data.SqlClient;
using Microsoft.EntityFrameworkCore.Diagnostics;
using Microsoft.Extensions.Logging;

namespace WebApplication.Data
{
    public class SqlMonitorInterceptor : DbCommandInterceptor
    {
        private readonly ILogger logger;

        public SqlMonitorInterceptor(ILogger logger)
        {
            this.logger = logger;
        }

        public override void CommandFailed(DbCommand command, CommandErrorEventData eventData)
        {
            if (eventData.Exception is not null)
            {
                logger.LogError(eventData.Exception, "Command:\r\n{Command}\r\nfailed with Exception.", GetGeneratedQuery(command));
            }
        }

        public override Task CommandFailedAsync(DbCommand command, CommandErrorEventData eventData, CancellationToken cancellationToken = default)
        {
            CommandFailed(command, eventData);

            return Task.CompletedTask;
        }

        public override int NonQueryExecuted(DbCommand command, CommandExecutedEventData eventData, int result)
        {
            CommandExecuted(eventData);

            return result;
        }

        public override ValueTask NonQueryExecutedAsync(DbCommand command, CommandExecutedEventData eventData, int result, CancellationToken cancellationToken = default)
        {
            CommandExecuted(eventData);

            return new ValueTask(result);
        }

        public override DbDataReader ReaderExecuted(DbCommand command, CommandExecutedEventData eventData, DbDataReader result)
        {
            CommandExecuted(eventData);

            return result;
        }

        public override ValueTask ReaderExecutedAsync(DbCommand command, CommandExecutedEventData eventData, DbDataReader result, CancellationToken cancellationToken = default)
        {
            CommandExecuted(eventData);

            return new ValueTask(result);
        }

        public override object ScalarExecuted(DbCommand command, CommandExecutedEventData eventData, object result)
        {
            CommandExecuted(eventData);

            return result;
        }

        public override ValueTask ScalarExecutedAsync(DbCommand command, CommandExecutedEventData eventData, object result, CancellationToken cancellationToken = default)
        {
            CommandExecuted(eventData);

            return new ValueTask(result);
        }

        private void CommandExecuted(CommandExecutedEventData eventData)
        {
            // Log command if non-async.
            if (!eventData.IsAsync)
            {
                logger.LogWarning("Non-async command used:\r\n{Command}", GetGeneratedQuery(eventData.Command));
            }

            // Log command if too slow.
            if (eventData.Duration > AppConfiguration.SqlPerformance_WarningThreshold)
            {
                logger.LogDebug("Query time ({Duration}ms) exceeded the threshold of {Threshold}ms. Command:\r\n{Command}",
                    eventData.Duration.TotalMilliseconds,
                    AppConfiguration.SqlPerformance_WarningThreshold.TotalMilliseconds,
                    GetGeneratedQuery(eventData.Command));
            }
        }

        private static string GetGeneratedQuery(DbCommand command)
        {
            var quotedParameterTypes = new DbType[] {
                DbType.Date, DbType.DateTime, DbType.DateTime2, DbType.DateTimeOffset,
                DbType.Guid, DbType.AnsiString, DbType.String,
                DbType.AnsiStringFixedLength, DbType.StringFixedLength
            };

            var sb = new StringBuilder();

            foreach (DbParameter p in command.Parameters)
            {
                sb.Append($"DECLARE {p.ParameterName}");
                sb.Append(' ');
                if (p is SqlParameter sqlParameter)
                {
                    sb.Append(sqlParameter.SqlDbType.ToString().ToLower());
                }
                else
                {
                    sb.Append(p.DbType.ToString().ToLower());
                }
                if (p.Size > 0)
                {
                    sb.Append("(" + (p.Size > 4000 ? "max" : p.Size.ToString()) + ")");
                }
                else if (p.Precision > 0)
                {
                    sb.Append("(" + p.Precision.ToString() + (p.Scale > 0 ? "," + p.Scale.ToString() : "") + ")");
                }

                sb.Append(' ');
                string pValStr = quotedParameterTypes.Contains(p.DbType) ? $"'{p.Value}'" : p.Value.ToString();
                sb.Append($"= {pValStr};");
                sb.AppendLine();
            }
            sb.AppendLine();
            sb.Append(command.CommandText);

            return sb.ToString();
        }
    }
}生成日志的示例:Query time (878.28ms) exceeded the threshold of 500ms. Command:

DECLARE @__8__locals1_entityDB_Id_0 int = 1094610;
DECLARE @__8__locals1_entityDB_CustomerId_1 uniqueidentifier = 'f47253a5-d660-4faf-408d-08d833e8e27b';
DECLARE @__phone_2 nvarchar(63) = '+79991111111';

SELECT COUNT(*)
FROM [Leads] AS [l]
WHERE (([l].[Id] <> @__8__locals1_entityDB_Id_0) AND ([l].[CustomerId] = @__8__locals1_entityDB_CustomerId_1)) AND ((@__phone_2 LIKE N'') OR (CHARINDEX(@__phone_2, [l].[PhoneNumbers]) > 0))你认为有什么改进/问题吗?
EN

回答 1

Code Review用户

回答已采纳

发布于 2022-02-04 08:42:15

CommandFailed

  • 我建议检查一下eventData is not null,而不仅仅是它的Exception
  • 次要的,但是对于日志严重性(LogError),您已经指出了command has failed with an exception
    • 因此,日志消息可能包含已经是日志本身一部分的事实。

CommandFailedAsync

  • 总是与Task.CompletedTask一起返回可能不是所有场景的最佳解决方案。
    • 例如,如果cancellationToken已经被请求取消,那么返回一个已取消的任务也是有意义的。

代码语言:javascript
复制
return cancellationToken.IsCancellationRequested 
       ? Task.FromCanceled(cancellationToken) 
       : Task.CompletedTask

CommandExecuted

  • 使用此方法的两个独立版本可能是有意义的。
    • 一个用于同步方法,另一个用于异步方法。

  • 每当您根据阈值>=检查Duration时使用可能是有意义的。
    • 这样,您也可以记录那些达到极限的方法,而不仅仅是那些超出的方法。

GetGeneratedQuery

  • 可以将quotedParameterTypes集合声明为静态类级别成员。
  • 将stringBuilder命名为sb似乎非常方便,但添加更有意义的名称(如commandStringBuilder )将提高可读性。
  • 然而,将迭代变量命名为p可能不是最好的选择
  • 第一个if-else块可以使用条件运算符进行简化。
代码语言:javascript
复制
sb.Append(dbParam is SqlParameter sqlParameter
    ? sqlParameter.SqlDbType.ToString().ToLower()
    : dbParam.DbType.ToString().ToLower());
  • p.Size > 4000:为这个神奇数字引入类级常量或使其可配置可能是有意义的。
  • 在第二个if-else块中,您也可以使用字符串内插,而不是串联。
    • 这样,您就可以避免在任何地方编写.ToString()

  • 根据它的名字,我不知道pValStr是什么
票数 3
EN
页面原文内容由Code Review提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://codereview.stackexchange.com/questions/273703

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档