问题描述
||
在NHibernate中使用MiniProfiler的数据库配置文件的最简单方法是什么?为了使探查器正常工作,我需要将NHibernate使用的ѭ0包裹在ѭ1中。
我对NHibernate的内部内容不太熟悉,所以我不知道所有扩展点在哪里。 (我注意到NHibernate的
ISession
具有Connection
属性,但它是只读的。)
解决方法
[更新]请参阅以下链接,了解使用RealProxy代理SqlCommand的版本的链接-现在支持批处理
博客http://blog.fearofaflatplanet.me.uk/mvcminiprofiler-and-nhibernate-take-2
要点https://gist.github.com/1110153
我已经接受了原始答案,但没有改变。 [/更新]
我已经通过实现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);
}
}
, 我在上面扩展了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(),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,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; }
}
, 尝试实现NHibernate.Connection.IConnectionProvider
(您可以继承DriverConnectionProvider
),在GetConnection()
中根据需要包装IDbConnection
。
使用properties10ѭ键在配置属性中插入连接提供程序。
, 如果有人感兴趣,我可以使用自定义Log4net附加程序完成集成。这样,我就不会碰到Connection对象,因此感到很安全。
大致轮廓如下:NHibernate发出sqlstrings作为调试语句,并且在log4net.xml中配置的附加程序在MiniProfiler上调用Start和Dispose。