Database.Log 不记录来自实体框架中的拦截器的附加 SQL

Posted

技术标签:

【中文标题】Database.Log 不记录来自实体框架中的拦截器的附加 SQL【英文标题】:Database.Log not logging appended SQL from interceptor in Entity Framework 【发布时间】:2021-10-12 03:37:21 【问题描述】:

我成功地将此代码附加到从实体框架创建的 SQL 查询中,并在 Scope 内使用拦截器,但随后调用的 Database.Log 在查询中没有显示 OPTION(RECOMPILE),所以我没有确保它甚至被执行。

调用代码

  using (new OptionRecompileScope(_divisionPoolsRepository.DataContext))
            
                divisionTeamResultsIds.AddRange(_divisionPoolsRepository.DataContext.DivisionBracketParticipants.Where(g => g.DivisionBracketParticipantPool.DivisionPoolId == divisionPoolId.Value).Select(g => g.DivisionGameTeamResultId).Distinct().ToList());
            

OptionRecompileScope.cs

private void AddOptionRecompile(IDbCommand command)
            
                command.CommandText += " OPTION(RECOMPILE)";
            

以上代码来自此链接EF 6 Parameter Sniffing

DataContext.cs

public class DataContext : DbContext
    
        private readonly ILogger _logger;

        public DataContext(ILogger logger)
        
            Database.SetInitializer<DataContext>(null);

            Configuration.LazyLoadingEnabled = false;
            Configuration.ProxyCreationEnabled = false;

            _logger = logger;

            if (Config.Debugging.LogDatabase)
            
                Database.Log = q => _logger.Debug(q);
            
        

整个作用域类

public class OptionRecompileScope : IDisposable
    
        private const string QueryHintText = " OPTION(RECOMPILE)";
        private readonly OptionRecompileDbCommandInterceptor interceptor;

        public OptionRecompileScope(DbContext context)
        
            interceptor = new OptionRecompileDbCommandInterceptor(context);
            DbInterception.Add(interceptor);
        

        public void Dispose()
        
            DbInterception.Remove(interceptor);
        

        private class OptionRecompileDbCommandInterceptor : IDbCommandInterceptor
        
            private readonly DbContext dbContext;

            internal OptionRecompileDbCommandInterceptor(DbContext dbContext)
            
                this.dbContext = dbContext;
            

            public void NonQueryExecuting(DbCommand command, DbCommandInterceptionContext<int> interceptionContext)
            
            

            public void NonQueryExecuted(DbCommand command, DbCommandInterceptionContext<int> interceptionContext)
            
            

            public void ReaderExecuting(DbCommand command, DbCommandInterceptionContext<DbDataReader> interceptionContext)
            
                if (ShouldIntercept(command, interceptionContext))
                
                    AddOptionRecompile(command);
                
            

            public void ReaderExecuted(DbCommand command, DbCommandInterceptionContext<DbDataReader> interceptionContext)
            
            

            public void ScalarExecuting(DbCommand command, DbCommandInterceptionContext<object> interceptionContext)
            
                if (ShouldIntercept(command, interceptionContext))
                
                    AddOptionRecompile(command);
                
            

            public void ScalarExecuted(DbCommand command, DbCommandInterceptionContext<object> interceptionContext)
            
            

            private void AddOptionRecompile(IDbCommand command)
            
                if (!command.CommandText.EndsWith(QueryHintText))
                
                    command.CommandText += QueryHintText;
                
            

            private bool ShouldIntercept(IDbCommand command, DbCommandInterceptionContext interceptionContext)
            
                return
                    command.CommandType == CommandType.Text &&
                    command is SqlCommand &&
                    interceptionContext.DbContexts.Any(interceptionDbContext => ReferenceEquals(interceptionDbContext, dbContext));
            
        
    

【问题讨论】:

你用DbInterception.Add(...)注册拦截器吗? 是的,代码有,我说列出的代码被命中了。范围添加它。 好的,只是为了确定。这里的一个快速测试指出这是一个顺序问题。如果Log 在添加拦截器后 被订阅,它将记录修改后的命令,否则记录原始命令。毫无疑问,这与记录器本身也是一个拦截器有关,它们作为管道运行。 您可以寻求像this 这样的解决方案。 IE。只需在启动时注册一次拦截器并打开或关闭其效果。 但是在datacontext构造函数中不是在另一个之前添加了记录器,并且db是另一个拦截器的作用域密码吗?为什么这对日志记录很重要是一个 lambda,并且除非调用查询,否则它不会被执行。 【参考方案1】:

看起来顺序是问题所在。我刚刚在 OptionRecompileScope 构造函数中读取了它,现在输出是正确的。

public OptionRecompileScope(DbContext context, ILogger logger)
        
            interceptor = new OptionRecompileDbCommandInterceptor(context);
            DbInterception.Add(interceptor);
            if (Config.Debugging.LogDatabase)
            
                context.Database.Log = q => logger.Debug(q);
            
        

【讨论】:

以上是关于Database.Log 不记录来自实体框架中的拦截器的附加 SQL的主要内容,如果未能解决你的问题,请参考以下文章

实体框架,查询包含上下文更改的dbset,而不调用保存更改

实体框架支持来自数据库优先方法的默认约束

防止在实体框架中的相关表实体上添加新记录

需要使用实体框架将大量记录插入数据库

跟踪从实体框架代码调用的查询

来自实体框架的 SqlException - 不允许新事务,因为会话中正在运行其他线程