You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

记录带参数值的NHibernate错误查询

靠谱的NHibernate失败查询记录方案

嘿,这个问题我之前做项目的时候也踩过坑,IInterceptor确实在记录失败查询的参数和线程安全上有局限。给你推荐两个实用的方案,尤其是第二个,几乎完美解决你的需求:

方案一:改进IInterceptor,用ThreadLocal实现线程安全的参数记录

如果你的NHibernate版本比较旧,没法用新的拦截器,可以这样优化:

  • 首先定义一个线程本地存储的容器,用来临时保存当前线程的SQL和参数:
    public static class QueryLoggingContext
    {
        private static readonly ThreadLocal<(string Sql, ICollection<object> Parameters)> _currentQuery = new ThreadLocal<(string, ICollection<object>)>();
    
        public static void SetCurrentQuery(string sql, ICollection<object> parameters)
        {
            _currentQuery.Value = (sql, parameters);
        }
    
        public static (string Sql, ICollection<object> Parameters)? GetCurrentQuery()
        {
            return _currentQuery.IsValueCreated ? _currentQuery.Value : null;
        }
    
        public static void Clear()
        {
            _currentQuery.Value = default;
        }
    }
    
  • 然后在全局异常处理中,捕获NHibernate的GenericADOException,取出上下文里的SQL和参数记录,然后清空上下文:
    try
    {
        // 执行NHibernate操作
    }
    catch (GenericADOException ex)
    {
        var queryContext = QueryLoggingContext.GetCurrentQuery();
        if (queryContext.HasValue)
        {
            // 记录日志,比如用log4net
            log.Error($"Failed query: {queryContext.Value.Sql}, Parameters: {string.Join(", ", queryContext.Value.Parameters)}", ex);
            QueryLoggingContext.Clear();
        }
        throw;
    }
    
    这个方案解决了线程安全问题,但IInterceptor本身没法直接拿到参数值,所以更推荐下面的方案。

方案二:使用IDbCommandInterceptor(NHibernate 5.2+)

NHibernate 5.2之后引入了IDbCommandInterceptor,专门用来拦截数据库命令的执行,能直接获取DbCommand对象,自然就能拿到参数值,而且每个命令都是绑定到当前会话的,完全线程安全:

  • 首先自定义一个实现IDbCommandInterceptor的类:
    public class FailedQueryLoggingInterceptor : IDbCommandInterceptor
    {
        private readonly ILog _log = LogManager.GetLogger(typeof(FailedQueryLoggingInterceptor));
    
        public void NonQueryExecuted(DbCommand command, DbCommandInterceptionContext<int> interceptionContext)
        {
            LogIfFailed(command, interceptionContext);
        }
    
        public void ReaderExecuted(DbCommand command, DbCommandInterceptionContext<DbDataReader> interceptionContext)
        {
            LogIfFailed(command, interceptionContext);
        }
    
        public void ScalarExecuted(DbCommand command, DbCommandInterceptionContext<object> interceptionContext)
        {
            LogIfFailed(command, interceptionContext);
        }
    
        private void LogIfFailed(DbCommand command, DbCommandInterceptionContext interceptionContext)
        {
            if (interceptionContext.Exception != null)
            {
                // 提取SQL和参数
                var sql = command.CommandText;
                var parameters = command.Parameters.Cast<DbParameter>()
                    .Select(p => $"{p.ParameterName} = {p.Value}")
                    .ToList();
    
                // 用log4net记录失败的查询、参数和异常
                _log.Error($"Failed to execute query:\nSQL: {sql}\nParameters: {string.Join("\n", parameters)}", interceptionContext.Exception);
            }
        }
    
        // 其他接口方法可以留空或者调用默认实现
        public DbCommand NonQueryExecuting(DbCommand command, DbCommandInterceptionContext<int> interceptionContext) => command;
        public DbCommand ReaderExecuting(DbCommand command, DbCommandInterceptionContext<DbDataReader> interceptionContext) => command;
        public DbCommand ScalarExecuting(DbCommand command, DbCommandInterceptionContext<object> interceptionContext) => command;
    }
    
  • 然后在配置NHibernate的时候,把这个拦截器注册进去:
    // 常规配置方式
    var configuration = new Configuration();
    configuration.AddInterceptor(new FailedQueryLoggingInterceptor());
    
    // Fluent NHibernate配置方式
    Fluently.Configure()
        .ExposeConfiguration(cfg => cfg.AddInterceptor(new FailedQueryLoggingInterceptor()))
        // 其他配置项...
    
    这个方案的核心优势:
    • 直接拿到DbCommand,参数名称和值都能轻松获取,完美解决参数记录难题
    • 每个命令都绑定到当前会话,不存在线程安全问题(NHibernate会话本身就是线程隔离的)
    • 只在命令执行失败时记录日志,性能开销极低
    • 能捕获所有数据库层面的异常,包括你提到的ORA错误

额外提示

如果项目用了依赖注入,可以把FailedQueryLoggingInterceptor注册为单例——它本身无状态,完全线程安全。另外,记录参数时要注意敏感信息(比如密码),可以在日志逻辑里过滤掉这类参数值,避免数据泄露。

内容的提问来源于stack exchange,提问作者Dries Schuddinck

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.19 04:18:32