entity framework6 – 定时查询

我正在使用Entity Framework 6,它是非常棒的数据库拦截器function,用于记录从应用程序数据库发送的查询。 但是,我正在努力为这些查询计时,我有一个长时间运行的查询,返回数十万到数百万行,因此需要大约6到15秒,具体取决于此查询将返回的数据量。 entity framework返回一个SqlDataReader,因为我无法获得获取结果所需的确切时间。 我想知道从查询发送到读取最后一行的时间的完整执行时间。 有没有办法可以做到。

这是我通常用于EF的记录器。

public class EFLoggerForTesting : IDbCommandInterceptor { static readonly ConcurrentDictionary m_StartTime = new ConcurrentDictionary(); public void ReaderExecuted(DbCommand command, DbCommandInterceptionContext interceptionContext) { Log(command, interceptionContext); } public void NonQueryExecuted(DbCommand command, DbCommandInterceptionContext interceptionContext) { Log(command, interceptionContext); } public void ScalarExecuted(DbCommand command, DbCommandInterceptionContext interceptionContext) { Log(command, interceptionContext); } private static void Log(DbCommand command, DbCommandInterceptionContext interceptionContext) { DateTime startTime; TimeSpan duration; m_StartTime.TryRemove(command, out startTime); if (startTime != default(DateTime)) { duration = DateTime.Now - startTime; } else duration = TimeSpan.Zero; var requestId =-1; string message; var parameters = new StringBuilder(); foreach (DbParameter param in command.Parameters) { parameters.AppendLine(param.ParameterName + " " + param.DbType + " = " + param.Value); } if (interceptionContext.Exception == null) { message = string.Format("Database call took {0} sec. RequestId {1} \r\nCommand:\r\n{2}", duration.TotalSeconds.ToString("N3"), requestId, parameters.ToString() + command.CommandText); } else { message = string.Format("EF Database call failed after {0} sec. RequestId {1} \r\nCommand:\r\n{2}\r\nError:{3} ", duration.TotalSeconds.ToString("N3"), requestId, parameters.ToString() + command.CommandText, interceptionContext.Exception); } Debug.WriteLine(message); } public void NonQueryExecuting(DbCommand command, DbCommandInterceptionContext interceptionContext) { OnStart(command); } public void ReaderExecuting(DbCommand command, DbCommandInterceptionContext interceptionContext) { OnStart(command); } public void ScalarExecuting(DbCommand command, DbCommandInterceptionContext interceptionContext) { OnStart(command); } private static void OnStart(DbCommand command) { m_StartTime.TryAdd(command, DateTime.Now); } } 

不幸的是,文档不存在,所以我不知道这是否适用于您的场景。

如果您使用的是Web应用程序,可以试试Glimpse: http : //getglimpse.com/ 。 否则,请尝试MiniProfiler: http : //miniprofiler.com/ 。

正如@Ricardo Peres的回答所暗示的那样,Glimpse对此很有好处。 它附带了Glimpse.Ado插件,可用于轻松分析任何DbConnection ,这是在此扩展的主要类。

Glimpse中ADO的手动集成点是将DbConnection包装在GlimpseDbConnection ,如本博客文章中所示: http : GlimpseDbConnection 。 像EF这样的其他提供商会自动与Glimpse.Ef软件包集成。

如果你仍然决定要手动实现这一点,我建议你自己包装DbConneciton并使用它来代替常规DbConnection 。 你可以做这个简单的计时。

你可以看看Glimpse如何在他们的github上做到这一点: https : //github.com/Glimpse/Glimpse/tree/master/source/Glimpse.Ado/AlternateType

或者,始终可以选择在存储库级别或方法级别添加此类日志记录,具体取决于您需要多少日志记录。

相当简单,但你不能使用System.Timers.Timer对象? 在EF代码之前调用start,在EF代码之后调用end。 如果你有异步代码,你可以调用.Result并删除等待,以便同步运行代码并计时。 除此之外,如果您使用SQL分析器并从另一个中减去一个值(Timer – Profiler),您将了解EF代码执行的时间。

下面是我原始DatabaseLogFormatter简化版本。 主要的区别是这个没有过滤,我没有记录实际的SQL查询或它的参数(因为我只对查询计时感兴趣)。 它在打开连接,执行查询以及何时再次关闭连接时进行记录。 正如@ aske-b 上面注意到使用DateTime (正如接受的答案所做的那样)并不是很准确。


 public class CustomDatabaseLogFormatter : IDbCommandInterceptor, IDbConnectionInterceptor { private readonly Action _writeAction; private readonly Stopwatch _stopwatch = new Stopwatch(); ///  /// Creates a formatter that will log every command from any context and also commands that do not originate from a context. ///  ///  /// This constructor is not used when a delegate is set on . Instead it can be /// used by setting the formatter directly using . ///  /// The delegate to which output will be sent. public CustomDatabaseLogFormatter(Action writeAction) { Check.NotNull(writeAction, "writeAction"); _writeAction = writeAction; } internal Action WriteAction { get { return _writeAction; } } ///  /// Writes the given string to the underlying write delegate. ///  /// The string to write. protected virtual void Write(string output) { _writeAction(output); } ///  /// The stopwatch used to time executions. This stopwatch is started at the end of /// , , and  /// methods and is stopped at the beginning of the , , /// and  methods. If these methods are overridden and the stopwatch is being used /// then the overrides should either call the base method or start/stop the stopwatch themselves. ///  /// The stopwatch. protected internal Stopwatch Stopwatch { get { return _stopwatch; } } private void RestartStopwatch() { Stopwatch.Restart(); } private void StopStopwatch() { Stopwatch.Stop(); } #region IDbCommandInterceptor ///  /// This method is called before a call to  or /// one of its async counterparts is made. /// Starts the stopwatch returned from . ///  /// The command being executed. /// Contextual information associated with the call. public virtual void NonQueryExecuting(DbCommand command, DbCommandInterceptionContext interceptionContext) { Check.NotNull(command, "command"); Check.NotNull(interceptionContext, "interceptionContext"); RestartStopwatch(); } ///  /// This method is called after a call to  or /// one of its async counterparts is made. /// Stops the stopwatch returned from  and calls . ///  /// The command being executed. /// Contextual information associated with the call. public virtual void NonQueryExecuted(DbCommand command, DbCommandInterceptionContext interceptionContext) { Check.NotNull(command, "command"); Check.NotNull(interceptionContext, "interceptionContext"); StopStopwatch(); Executed(command, interceptionContext); } ///  /// This method is called before a call to  or /// one of its async counterparts is made. /// Starts the stopwatch returned from . ///  /// The command being executed. /// Contextual information associated with the call. public virtual void ReaderExecuting(DbCommand command, DbCommandInterceptionContext interceptionContext) { Check.NotNull(command, "command"); Check.NotNull(interceptionContext, "interceptionContext"); RestartStopwatch(); } ///  /// This method is called after a call to  or /// one of its async counterparts is made. /// Stops the stopwatch returned from  and calls . ///  /// The command being executed. /// Contextual information associated with the call. public virtual void ReaderExecuted(DbCommand command, DbCommandInterceptionContext interceptionContext) { Check.NotNull(command, "command"); Check.NotNull(interceptionContext, "interceptionContext"); StopStopwatch(); Executed(command, interceptionContext); } ///  /// This method is called before a call to  or /// one of its async counterparts is made. /// Starts the stopwatch returned from . ///  /// The command being executed. /// Contextual information associated with the call. public virtual void ScalarExecuting(DbCommand command, DbCommandInterceptionContext interceptionContext) { Check.NotNull(command, "command"); Check.NotNull(interceptionContext, "interceptionContext"); RestartStopwatch(); } ///  /// This method is called after a call to  or /// one of its async counterparts is made. /// Stops the stopwatch returned from  and calls /// . ///  /// The command being executed. /// Contextual information associated with the call. public virtual void ScalarExecuted(DbCommand command, DbCommandInterceptionContext interceptionContext) { Check.NotNull(command, "command"); Check.NotNull(interceptionContext, "interceptionContext"); StopStopwatch(); Executed(command, interceptionContext); } ///  /// Called whenever a command has completed executing. Calls . ///  /// The type of the operation's results. /// The command that was executed. /// Contextual information associated with the command. public virtual void Executed(DbCommand command, DbCommandInterceptionContext interceptionContext) { Check.NotNull(command, "command"); Check.NotNull(interceptionContext, "interceptionContext"); LogResult(command, interceptionContext); } ///  /// Called to log the result of executing a command. ///  /// The type of the operation's results. /// The command being logged. /// Contextual information associated with the command. public virtual void LogResult(DbCommand command, DbCommandInterceptionContext interceptionContext) { Check.NotNull(command, "command"); Check.NotNull(interceptionContext, "interceptionContext"); var stopwatch = Stopwatch; if (interceptionContext.Exception != null) { Write( String.Format(StringResources.CommandLogFailed, stopwatch.ElapsedMilliseconds, interceptionContext.Exception.Message) ); } else if (interceptionContext.TaskStatus.HasFlag(TaskStatus.Canceled)) { Write(String.Format(StringResources.CommandLogCanceled, stopwatch.ElapsedMilliseconds)); } else { var result = interceptionContext.Result; var resultString = (object)result == null ? "null" : (result is DbDataReader) ? result.GetType().Name : result.ToString(); Write(String.Format(StringResources.CommandLogComplete, stopwatch.ElapsedMilliseconds, resultString)); } } #endregion #region IDbConnectionInterceptor public void BeginningTransaction(DbConnection connection, BeginTransactionInterceptionContext interceptionContext) { } public void BeganTransaction(DbConnection connection, BeginTransactionInterceptionContext interceptionContext) { } public void Closing(DbConnection connection, DbConnectionInterceptionContext interceptionContext) { } ///  /// Called after  is invoked. ///  /// The connection that was closed. /// Contextual information associated with the call. public void Closed(DbConnection connection, DbConnectionInterceptionContext interceptionContext) { Check.NotNull(connection, "connection"); Check.NotNull(interceptionContext, "interceptionContext"); if (interceptionContext.Exception != null) { Write(String.Format(StringResources.ConnectionCloseErrorLog, DateTimeOffset.UtcNow, interceptionContext.Exception.Message)); } else { Write(String.Format(StringResources.ConnectionClosedLog, DateTimeOffset.UtcNow)); } } public void ConnectionStringGetting(DbConnection connection, DbConnectionInterceptionContext interceptionContext) { } public void ConnectionStringGot(DbConnection connection, DbConnectionInterceptionContext interceptionContext) { } public void ConnectionStringSetting(DbConnection connection, DbConnectionPropertyInterceptionContext interceptionContext) { } public void ConnectionStringSet(DbConnection connection, DbConnectionPropertyInterceptionContext interceptionContext) { } public void ConnectionTimeoutGetting(DbConnection connection, DbConnectionInterceptionContext interceptionContext) { } public void ConnectionTimeoutGot(DbConnection connection, DbConnectionInterceptionContext interceptionContext) { } public void DatabaseGetting(DbConnection connection, DbConnectionInterceptionContext interceptionContext) { } public void DatabaseGot(DbConnection connection, DbConnectionInterceptionContext interceptionContext) { } public void DataSourceGetting(DbConnection connection, DbConnectionInterceptionContext interceptionContext) { } public void DataSourceGot(DbConnection connection, DbConnectionInterceptionContext interceptionContext) { } public void Disposing(DbConnection connection, DbConnectionInterceptionContext interceptionContext) { } public void Disposed(DbConnection connection, DbConnectionInterceptionContext interceptionContext) { } public void EnlistingTransaction(DbConnection connection, EnlistTransactionInterceptionContext interceptionContext) { } public void EnlistedTransaction(DbConnection connection, EnlistTransactionInterceptionContext interceptionContext) { } public void Opening(DbConnection connection, DbConnectionInterceptionContext interceptionContext) { } ///  /// Called after  or its async counterpart is invoked. ///  /// The connection that was opened. /// Contextual information associated with the call. public void Opened(DbConnection connection, DbConnectionInterceptionContext interceptionContext) { Check.NotNull(connection, "connection"); Check.NotNull(interceptionContext, "interceptionContext"); if (interceptionContext.Exception != null) { Write( interceptionContext.IsAsync ? String.Format(StringResources.ConnectionOpenErrorLogAsync, DateTimeOffset.UtcNow, interceptionContext.Exception.Message) : String.Format(StringResources.ConnectionOpenErrorLog, DateTimeOffset.UtcNow, interceptionContext.Exception.Message)); } else if (interceptionContext.TaskStatus.HasFlag(TaskStatus.Canceled)) { Write(String.Format(StringResources.ConnectionOpenCanceledLog, DateTimeOffset.UtcNow)); } else { Write( interceptionContext.IsAsync ? String.Format(StringResources.ConnectionOpenedLogAsync, DateTimeOffset.UtcNow) : String.Format(StringResources.ConnectionOpenedLog, DateTimeOffset.UtcNow)); } } public void ServerVersionGetting(DbConnection connection, DbConnectionInterceptionContext interceptionContext) { } public void ServerVersionGot(DbConnection connection, DbConnectionInterceptionContext interceptionContext) { } public void StateGetting(DbConnection connection, DbConnectionInterceptionContext interceptionContext) { } public void StateGot(DbConnection connection, DbConnectionInterceptionContext interceptionContext) { } #endregion } 

 internal class Check { public static T NotNull(T value, string parameterName) where T : class { if (value == null) { throw new ArgumentNullException(parameterName); } return value; } } 

 StringResources.resx: CommandLogCanceled Canceled in {0} ms{1} CommandLogComplete Completed in {0} ms with result: {1} CommandLogFailed Failed in {0} ms with error: {1} ConnectionClosedLog Closed connection at {0} ConnectionCloseErrorLog Failed to close connection at {0} with error: {1} ConnectionOpenCanceledLog Cancelled open connection at {0} ConnectionOpenedLog Opened connection at {0} ConnectionOpenedLogAsync Opened connection asynchronously at {0} ConnectionOpenErrorLog Failed to open connection at {0} with error: {1} ConnectionOpenErrorLogAsync Failed to open connection asynchronously at {0} with error: {1} 

您可以尝试这里解释的示例

在这个例子中,我们在命令开始执行时启动Stopwatch ,并在命令完成时停止Stopwatch ,这样我们就可以识别慢速查询并记录它们。

 public class SqlMonitorInterceptor : IDbCommandInterceptor { private static readonly ILog logger = LogManager.GetCurrentClassLogger(); private static readonly int sqlWarningThresholdMs = int.Parse(ConfigurationManager.AppSettings["sqlPerformance_warningThresholdMilliseconds"]); private readonly Stopwatch _stopwatch = new Stopwatch(); public void NonQueryExecuting(DbCommand command, DbCommandInterceptionContext interceptionContext) { CommandExecuting(); } public void NonQueryExecuted(DbCommand command, DbCommandInterceptionContext interceptionContext) { CommandExecuted(command, interceptionContext); } public void ReaderExecuting(DbCommand command, DbCommandInterceptionContext interceptionContext) { CommandExecuting(); } public void ReaderExecuted(DbCommand command, DbCommandInterceptionContext interceptionContext) { CommandExecuted(command, interceptionContext); } public void ScalarExecuting(DbCommand command, DbCommandInterceptionContext interceptionContext) { CommandExecuting(); } public void ScalarExecuted(DbCommand command, DbCommandInterceptionContext interceptionContext) { CommandExecuted(command, interceptionContext); } private void CommandExecuting() { _stopwatch.Restart(); } private void CommandExecuted(DbCommand command, DbCommandInterceptionContext interceptionContext) { _stopwatch.Stop(); LogIfError(command, interceptionContext); LogIfTooSlow(command, _stopwatch.Elapsed); } private void LogIfError(DbCommand command, DbCommandInterceptionContext interceptionContext) { if (interceptionContext.Exception != null) { logger.ErrorFormat("Command {0} failed with exception {1}", command.CommandText, interceptionContext.Exception); } } private void LogIfTooSlow(DbCommand command, TimeSpan completionTime) { if (completionTime.TotalMilliseconds > sqlWarningThresholdMs) { logger.WarnFormat("Query time ({0}ms) exceeded the threshold of {1}ms. Command: \"{2}\"", completionTime.TotalMilliseconds, sqlWarningThresholdMs, command.CommandText); } } }