【问题标题】:Using MiniProfiler's database profiling with NHibernate在 NHibernate 中使用 MiniProfiler 的数据库分析
【发布时间】:2011-09-16 11:23:41
【问题描述】:

在 NHibernate 中使用MiniProfiler 的数据库分析最简单的方法是什么?为了使分析器工作,我需要将 NHibernate 使用的 DbConnection 包装在 ProfiledDbConnection 中。

我对 NHibernate 的内部结构不太熟悉,所以我不知道所有可扩展点在哪里。 (我注意到 NHibernate ISession 有一个 Connection 属性,但它是只读的。)

【问题讨论】:

    标签: nhibernate mvc-mini-profiler


    【解决方案1】:

    尝试实现NHibernate.Connection.IConnectionProvider(您可以只继承DriverConnectionProvider),在GetConnection() 中根据需要包装IDbConnection

    使用配置属性中的 Environment.ConnectionProvider 键插入连接提供程序。

    【讨论】:

    • 看起来不错,但 mvc-mini-profiler 的 ProfiledDbConnection 需要具体的 DbConnection,而不是 GetConnection() 方法返回的 IDbConnection。 :(
    • @Brant:这是一个 hack,但我很确定您可以将其转换为 DbConnection。否则,请研究为什么 mvc-mini-profiler 真的需要 DbConnection,也许它可以与 IDbConnection 一起工作。
    • 嗯,这也不太行得通,似乎 NHibernate 期望 IDbConnectionSqlConnection 并尝试转换它。除非有办法解决这个问题,否则我猜不可能将 mvc-mini-profiler 包与 NHibernate 一起使用。
    • @Brant:如果您将 SQL Server 与批处理一起使用,则可能会发生这种情况。所以尝试禁用 NH 批处理。
    • 我实际上设法使用 NHibernate 批处理。进行了大量的黑客攻击,但明天将发布代码。
    【解决方案2】:

    [更新] 请查看以下链接了解使用 RealProxy 代理 SqlCommand 的版本 - 现在支持批处理

    我保留了原始答案,因为它被接受了。 [/更新]

    我已经通过实现一个 Profiled Client Driver(下面的 Sql Server 2008 示例)设法部分地使其工作 - 这适用于简单的示例,但是我还没有找到 NH 批处理的解决方案(它试图将命令转换回 SqlCommand)

    public class ProfiledSql2008ClientDriver : Sql2008ClientDriver
    {
        public override IDbCommand CreateCommand()
        {
            return new ProfiledDbCommand(
                base.CreateCommand() as DbCommand, 
                null,
                MiniProfiler.Current);
        }
    
        public override IDbConnection CreateConnection()
        {
            return ProfiledDbConnection.Get(
                base.CreateConnection() as DbConnection, 
                MiniProfiler.Current);
        }
    }
    

    【讨论】:

    • MiniProfiler (1.9) 的当前版本需要稍作修改。对 ProfiledDbConnection.Get 的调用需要替换为构造函数:return new ProfiledDbConnection(base.CreateConnection() as DbConnection, MiniProfiler.Current);
    【解决方案3】:

    我扩展了上面的 Roberts 答案以使用 NHibernate 批处理。这里有很多代码,因此可以缩短,其中一些基于客户端驱动程序的 nHibernate 源代码。

    <property name="connection.driver_class">YoureOnTime.Data.ProfiledSqlClientDriver, YoureOnTime.Common</property>
    
    
    public class ProfiledSqlClientDriver : DriverBase, IEmbeddedBatcherFactoryProvider
    {
        public override IDbConnection CreateConnection()
        {
            return new ProfiledSqlDbConnection(
                new SqlConnection(), 
                MiniProfiler.Current);
        }
    
        public override IDbCommand CreateCommand()
        {
            return new ProfiledSqlDbCommand(
                new SqlCommand(),
                null,
                MiniProfiler.Current);
        }
    
        public override bool UseNamedPrefixInSql
        {
            get { return true; }
        }
    
        public override bool UseNamedPrefixInParameter
        {
            get { return true; }
        }
    
        public override string NamedPrefix
        {
            get { return "@"; }
        }
    
        public override bool SupportsMultipleOpenReaders
        {
            get { return false; }
        }
    
        public static void SetParameterSizes(IDataParameterCollection parameters, SqlType[] parameterTypes)
        {
            for (int i = 0; i < parameters.Count; i++)
            {
                SetVariableLengthParameterSize((IDbDataParameter)parameters[i], parameterTypes[i]);
            }
        }
    
        private const int MaxAnsiStringSize = 8000;
        private const int MaxBinarySize = MaxAnsiStringSize;
        private const int MaxStringSize = MaxAnsiStringSize / 2;
        private const int MaxBinaryBlobSize = int.MaxValue;
        private const int MaxStringClobSize = MaxBinaryBlobSize / 2;
        private const byte MaxPrecision = 28;
        private const byte MaxScale = 5;
        private const byte MaxDateTime2 = 8;
        private const byte MaxDateTimeOffset = 10;
    
        private static void SetDefaultParameterSize(IDbDataParameter dbParam, SqlType sqlType)
        {
            switch (dbParam.DbType)
            {
                case DbType.AnsiString:
                case DbType.AnsiStringFixedLength:
                    dbParam.Size = MaxAnsiStringSize;
                    break;
    
                case DbType.Binary:
                    if (sqlType is BinaryBlobSqlType)
                    {
                        dbParam.Size = MaxBinaryBlobSize;
                    }
                    else
                    {
                        dbParam.Size = MaxBinarySize;
                    }
                    break;
                case DbType.Decimal:
                    dbParam.Precision = MaxPrecision;
                    dbParam.Scale = MaxScale;
                    break;
                case DbType.String:
                case DbType.StringFixedLength:
                    dbParam.Size = IsText(dbParam, sqlType) ? MaxStringClobSize : MaxStringSize;
                    break;
                case DbType.DateTime2:
                    dbParam.Size = MaxDateTime2;
                    break;
                case DbType.DateTimeOffset:
                    dbParam.Size = MaxDateTimeOffset;
                    break;
            }
        }
    
        private static bool IsText(IDbDataParameter dbParam, SqlType sqlType)
        {
            return (sqlType is StringClobSqlType) || (sqlType.LengthDefined && sqlType.Length > MsSql2000Dialect.MaxSizeForLengthLimitedStrings &&
                (DbType.String == dbParam.DbType || DbType.StringFixedLength == dbParam.DbType));
        }
    
        private static void SetVariableLengthParameterSize(IDbDataParameter dbParam, SqlType sqlType)
        {
            SetDefaultParameterSize(dbParam, sqlType);
    
            // Override the defaults using data from SqlType.
            if (sqlType.LengthDefined && !IsText(dbParam, sqlType))
            {
                dbParam.Size = sqlType.Length;
            }
    
            if (sqlType.PrecisionDefined)
            {
                dbParam.Precision = sqlType.Precision;
                dbParam.Scale = sqlType.Scale;
            }
        }
    
        public override IDbCommand GenerateCommand(CommandType type, SqlString sqlString, SqlType[] parameterTypes)
        {
            IDbCommand command = base.GenerateCommand(type, sqlString, parameterTypes);
            //if (IsPrepareSqlEnabled)
            {
                SetParameterSizes(command.Parameters, parameterTypes);
            }
            return command;
        }
    
        public override bool SupportsMultipleQueries
        {
            get { return true; }
        }
    
        #region IEmbeddedBatcherFactoryProvider Members
    
        System.Type IEmbeddedBatcherFactoryProvider.BatcherFactoryClass
        {
            get { return typeof(ProfiledSqlClientBatchingBatcherFactory); }
        }
    
        #endregion
    }
    
    
    public class ProfiledSqlClientBatchingBatcher : AbstractBatcher
    {
        private int batchSize;
        private int totalExpectedRowsAffected;
        private SqlClientSqlCommandSet currentBatch;
        private StringBuilder currentBatchCommandsLog;
        private readonly int defaultTimeout;
    
        public ProfiledSqlClientBatchingBatcher(ConnectionManager connectionManager, IInterceptor interceptor)
            : base(connectionManager, interceptor)
        {
            batchSize = Factory.Settings.AdoBatchSize;
            defaultTimeout = PropertiesHelper.GetInt32(NHibernate.Cfg.Environment.CommandTimeout, NHibernate.Cfg.Environment.Properties, -1);
    
            currentBatch = CreateConfiguredBatch();
            //we always create this, because we need to deal with a scenario in which
            //the user change the logging configuration at runtime. Trying to put this
            //behind an if(log.IsDebugEnabled) will cause a null reference exception 
            //at that point.
            currentBatchCommandsLog = new StringBuilder().AppendLine("Batch commands:");
        }
    
        public override int BatchSize
        {
            get { return batchSize; }
            set { batchSize = value; }
        }
    
        protected override int CountOfStatementsInCurrentBatch
        {
            get { return currentBatch.CountOfCommands; }
        }
    
        public override void AddToBatch(IExpectation expectation)
        {
            totalExpectedRowsAffected += expectation.ExpectedRowCount;
            IDbCommand batchUpdate = CurrentCommand;
    
            string lineWithParameters = null;
            var sqlStatementLogger = Factory.Settings.SqlStatementLogger;
            if (sqlStatementLogger.IsDebugEnabled || log.IsDebugEnabled)
            {
                lineWithParameters = sqlStatementLogger.GetCommandLineWithParameters(batchUpdate);
                var formatStyle = sqlStatementLogger.DetermineActualStyle(FormatStyle.Basic);
                lineWithParameters = formatStyle.Formatter.Format(lineWithParameters);
                currentBatchCommandsLog.Append("command ")
                    .Append(currentBatch.CountOfCommands)
                    .Append(":")
                    .AppendLine(lineWithParameters);
            }
            if (log.IsDebugEnabled)
            {
                log.Debug("Adding to batch:" + lineWithParameters);
            }
            currentBatch.Append(((ProfiledSqlDbCommand)batchUpdate).Command);
    
            if (currentBatch.CountOfCommands >= batchSize)
            {
                ExecuteBatchWithTiming(batchUpdate);
            }
        }
    
        protected void ProfiledPrepare(IDbCommand cmd)
        {
            try
            {
                IDbConnection sessionConnection = ConnectionManager.GetConnection();
    
                if (cmd.Connection != null)
                {
                    // make sure the commands connection is the same as the Sessions connection
                    // these can be different when the session is disconnected and then reconnected
                    if (cmd.Connection != sessionConnection)
                    {
                        cmd.Connection = sessionConnection;
                    }
                }
                else
                {
                    cmd.Connection = (sessionConnection as ProfiledSqlDbConnection).Connection;
                }
    
                ProfiledSqlDbTransaction trans = (ProfiledSqlDbTransaction)typeof(NHibernate.Transaction.AdoTransaction).InvokeMember("trans", System.Reflection.BindingFlags.GetField | System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance, null, ConnectionManager.Transaction, null);
                if (trans != null)
                    cmd.Transaction = trans.Transaction;
                Factory.ConnectionProvider.Driver.PrepareCommand(cmd);
            }
            catch (InvalidOperationException ioe)
            {
                throw new ADOException("While preparing " + cmd.CommandText + " an error occurred", ioe);
            }
        }
    
        protected override void DoExecuteBatch(IDbCommand ps)
        {
            log.DebugFormat("Executing batch");
            CheckReaders();
            ProfiledPrepare(currentBatch.BatchCommand);
            if (Factory.Settings.SqlStatementLogger.IsDebugEnabled)
            {
                Factory.Settings.SqlStatementLogger.LogBatchCommand(currentBatchCommandsLog.ToString());
                currentBatchCommandsLog = new StringBuilder().AppendLine("Batch commands:");
            }
    
            int rowsAffected;
            try
            {
                rowsAffected = currentBatch.ExecuteNonQuery();
            }
            catch (DbException e)
            {
                throw ADOExceptionHelper.Convert(Factory.SQLExceptionConverter, e, "could not execute batch command.");
            }
    
            Expectations.VerifyOutcomeBatched(totalExpectedRowsAffected, rowsAffected);
    
            currentBatch.Dispose();
            totalExpectedRowsAffected = 0;
            currentBatch = CreateConfiguredBatch();
        }
    
        private SqlClientSqlCommandSet CreateConfiguredBatch()
        {
            var result = new SqlClientSqlCommandSet();
            if (defaultTimeout > 0)
            {
                try
                {
                    result.CommandTimeout = defaultTimeout;
                }
                catch (Exception e)
                {
                    if (log.IsWarnEnabled)
                    {
                        log.Warn(e.ToString());
                    }
                }
            }
    
            return result;
        }
    }
    
    
    public class ProfiledSqlClientBatchingBatcherFactory : IBatcherFactory
    {
        public virtual IBatcher CreateBatcher(ConnectionManager connectionManager, IInterceptor interceptor)
        {
            return new ProfiledSqlClientBatchingBatcher(connectionManager, interceptor);
        }
    }
    
    
    public class ProfiledSqlDbCommand : ProfiledDbCommand
    {
        public ProfiledSqlDbCommand(SqlCommand cmd, SqlConnection conn, MiniProfiler profiler)
            : base(cmd, conn, profiler)
        {
            Command = cmd;
        }
    
        public SqlCommand Command { get; set; }
    
        private DbTransaction _trans;
    
        protected override DbTransaction DbTransaction
        {
            get { return _trans; }
            set
            {
                this._trans = value;
                ProfiledSqlDbTransaction awesomeTran = value as ProfiledSqlDbTransaction;
                Command.Transaction = awesomeTran == null ? (SqlTransaction)value : awesomeTran.Transaction;
            }
        }
    }
    
    
    
    public class ProfiledSqlDbConnection : ProfiledDbConnection
    {
        public ProfiledSqlDbConnection(SqlConnection connection, MiniProfiler profiler)
            : base(connection, profiler)
        {
            Connection = connection;
        }
    
        public SqlConnection Connection { get; set; }
    
        protected override DbTransaction BeginDbTransaction(System.Data.IsolationLevel isolationLevel)
        {
            return new ProfiledSqlDbTransaction(Connection.BeginTransaction(isolationLevel), this);
        }       
    
    }
    
    
    public class ProfiledSqlDbTransaction : ProfiledDbTransaction
    {
        public ProfiledSqlDbTransaction(SqlTransaction transaction, ProfiledDbConnection connection)
            : base(transaction, connection)
        {
            Transaction = transaction;
        }
    
        public SqlTransaction Transaction { get; set; }
    }
    

    【讨论】:

    • 谢谢!与我的 FluentNhibernate 设置完美搭配!
    • 感谢您提供此信息。我在 ProfiledSqlDbCommand.DbTransaction 设置器中遇到了一个无效的强制转换异常,但是一旦我评论说一切似乎都正常工作。是因为意外还是我只是注释掉了一些重要的东西?我所有的单元测试都通过该连接访问数据库,但我可能错过了一些东西。
    【解决方案4】:

    如果有人感兴趣,我已经使用自定义 Log4net appender 进行了集成。这样我就不会弄乱 Connection 对象了。

    大致大致如下:NHibernate 发出 sqlstrings 作为调试语句,log4net.xml 中配置的 appender 调用 MiniProfiler 上的 Start 和 Dispose。

    【讨论】:

    • 嘿@Konstantin,这听起来像是一种新颖的方法。你能发布一个你所构建的链接吗?
    猜你喜欢
    • 2020-01-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-11-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-12-18
    相关资源
    最近更新 更多