截获包含 NHibernate 生成的参数值的 SQL 语句

本文关键字:参数 SQL 语句 包含 NHibernate | 更新日期: 2023-09-27 17:57:07

我使用一个简单的拦截器来拦截nhibernate为记录目的而生成的sql字符串,它工作正常。

public class SessionManagerSQLInterceptor : EmptyInterceptor, IInterceptor
    {
        NHibernate.SqlCommand.SqlString IInterceptor.OnPrepareStatement(NHibernate.SqlCommand.SqlString sql)
        {
            NHSessionManager.Instance.NHibernateSQL = sql.ToString();
            return sql;
        }
    }

但是,这会捕获没有参数值的 sql 语句。它们被替换为"?"

例如:....哪里USER0_。用户名 = ?

到目前为止,我发现的唯一替代方法是使用 log4nets nhibernate.sql appender,它记录包含参数值的 sql 语句,但这对我没有好处。

我需要使用拦截器,例如。 当我捕获异常时,我想记录导致持久性问题的特定SQL语句,包括它包含的值并将其记录在邮件等。与进入日志文件查找导致问题的查询相比,这大大加快了调试速度。

如何获取完整的 sql 语句,包括 nhibernate 在运行时生成的参数值?

截获包含 NHibernate 生成的参数值的 SQL 语句

以下是(粗略的)我所做的:

  1. 创建 IDbCommand 接口的自定义实现,该接口在内部将所有工作委托给SqlCommand的实际工作(假设出于讨论目的调用LoggingDbCommand)。

  2. 创建 NHibernate 类的派生类 SqlClientDriver 。它应该看起来像这样:

    public class LoggingSqlClientDriver : SqlClientDriver
    {
        public override IDbCommand CreateCommand()
        {
            return new LoggingDbCommand(base.CreateCommand());
        }
    }
    
  3. 在 NHibernate 配置中注册客户端驱动程序(有关详细信息,请参阅 NHibernate 文档)。

请注意,我为 NHibernate 1.1.2 完成了所有这些操作,因此新版本可能需要进行一些更改。但我想这个想法本身仍然有效。

好的,真正的肉将在您的LoggingDbCommand实施中。我只会给你起草一些示例方法实现,但我想你会明白的,并且可以对其他 Execute*() 方法做同样的事情。

public int ExecuteNonQuery()
{
    try
    {
        // m_command holds the inner, true, SqlCommand object.
        return m_command.ExecuteNonQuery();
    }
    catch
    {
        LogCommand();
        throw; // pass exception on!
    }
}

当然,胆量在 LogCommand() 方法中,您可以在其中"完全访问"执行命令的所有详细信息:

  • 命令文本(其中的参数占位符如指定)到m_command.CommandText
  • 参数及其值一直传递到 m_command.Parameters 集合

剩下要做的(我已经完成了,但由于合同而无法发布 - 蹩脚但真实,对不起)是将该信息组装成适当的 SQL 字符串(提示:不要费心替换命令文本中的参数,只需像 NHibernate 自己的记录器一样将它们列在下面)。

侧边栏:如果异常被认为是致命的(AccessViolationException,OOM等),您可能甚至希望避免尝试记录,以确保您不会因为面对已经非常灾难性的事情而尝试登录而使事情变得更糟。

例:

try
{
   // ... same as above ...
}
catch (Exception ex)
{
   if (!(ex is OutOfMemoryException || ex is AccessViolationException || /* others */)
     LogCommand();
   throw;  // rethrow! original exception.
}

这样做更简单(适用于所有NH版本):

public class LoggingSqlClientDriver : SqlClientDriver
{
    public override IDbCommand GenerateCommand(CommandType type, NHibernate.SqlCommand.SqlString sqlString, NHibernate.SqlTypes.SqlType[] parameterTypes)
    {
        SqlCommand command = (SqlCommand)base.GenerateCommand(type, sqlString, parameterTypes);
        LogCommand(command);
        return command;
    }}

只是一个想法:你能实现一个新的log4net-appender吗,它接受所有sqlstatement(使用参数调试)并保存最后一个。当发生错误时,您可以向他询问最后一个 sql语句并通过电子邮件发送。

LoggerFor中实现自定义ILoggerFactory和过滤,以keyName等于 NHibernate.SQL 并通过 LoggerProvider.SetLoggersFactory 设置。适用于SQLite驱动程序,应该适用于其他。

默认情况下,LoggerProvider通过反射创建log4net(如果显示log4net程序集)。因此,最好的实现方式是自定义ILoggerFactory将日志委托为默认值,直到请求NHibernate.SQL日志。