【发布时间】:2017-03-13 08:52:14
【问题描述】:
我正在尝试使用IDbInterceptor 来尽可能准确地为实体框架的查询执行计时,实现Jonathan Allen 的answer to a similar question 的变体:
public class PerformanceLogDbCommendInterceptor : IDbCommandInterceptor
{
static readonly ConcurrentDictionary<DbCommand, DateTime> _startTimes =
new ConcurrentDictionary<DbCommand, DateTime>();
public void ReaderExecuted(DbCommand command,
DbCommandInterceptionContext<DbDataReader> interceptionContext)
{
Log(command, interceptionContext);
}
public void NonQueryExecuted(DbCommand command,
DbCommandInterceptionContext<int> interceptionContext)
{
Log(command, interceptionContext);
}
public void ScalarExecuted(DbCommand command,
DbCommandInterceptionContext<object> interceptionContext)
{
Log(command, interceptionContext);
}
private static void Log<T>(DbCommand command,
DbCommandInterceptionContext<T> interceptionContext)
{
DateTime startTime;
TimeSpan duration;
if(!_startTimes.TryRemove(command, out startTime))
{
//Log exception
return;
}
DateTime now = DateTime.UtcNow;
duration = now - startTime;
string requestGUID = Guid.Empty.ToString();
var context = interceptionContext.DbContexts.SingleOrDefault();
if (context == null)
{
//Log Exception
}
else
{
var businessContext = context as MyDb;
if (businessContext == null)
{
//Log Exception
}
else
{
requestGUID = businessContext.RequestGUID.ToString();
}
}
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"
+ $" {duration.TotalMilliseconds.ToString("N3")} ms."
+ $" RequestGUID {requestGUID}"
//+ $" \r\nCommand:\r\n{parameters.ToString() + command.CommandText}");
}
else
{
message = string.Format($"EF Database call failed after"
+ $" {duration.TotalMilliseconds.ToString("N3")} ms."
+ $" RequestGUID {requestGUID}"
+ $" \r\nCommand:\r\n{(parameters.ToString() + command.CommandText)}"
+ $"\r\nError:{interceptionContext.Exception} ");
}
if (duration == TimeSpan.Zero)
{
message += $" \r\nTime: start: {startTime.ToString("hh:mm:ss fffffff")}"
+ $" | now: {now.ToString("hh:mm:ss fffffff")}"
+ $" \r\n \r\nCommand:\r\n"
+ $"{parameters.ToString() + command.CommandText}";
}
System.Diagnostics.Debug.WriteLine(message);
}
public void NonQueryExecuting(DbCommand command,
DbCommandInterceptionContext<int> interceptionContext)
{
OnStart(command);
}
public void ReaderExecuting(DbCommand command,
DbCommandInterceptionContext<DbDataReader> interceptionContext)
{
OnStart(command);
}
public void ScalarExecuting(DbCommand command,
DbCommandInterceptionContext<object> interceptionContext)
{
OnStart(command);
}
private static void OnStart(DbCommand command)
{
_startTimes.TryAdd(command, DateTime.UtcNow);
}
}
奇怪的是,每 10 个左右的查询执行一次需要 0 个滴答声。它似乎只在我异步运行它时发生,同时有几个查询。需要注意的另一件事是,当我再次查询相同的少数查询时,并不总是相同的查询需要 0 个滴答声。
此外,我目前正在测试的数据库位于本地网络上,而不是本地机器上 - 对它的 ping 时间是 0-1 毫秒 - 所以即使数据被缓存,我也看不到怎么可能需要 0 个滴答声。
顺便说一句,大多数查询花费的时间可疑地接近 1、2 和 3 毫秒(例如 0.997 毫秒到 1.003 毫秒)。对我来说,这听起来像是操作系统旋转线程 cpu-time 和/或 1ms 睡眠。我不介意这种情况发生,但我只想知道为什么,这样我就可以解释结果中的不准确之处。
这可能与ConcurrentDictionary 有关。但是当我现在进行测试时,我目前只调用一次异步(WCF)方法,等待每个异步数据库调用,所以据我了解,它甚至不应该一次启动更多调用。这是所谓的示例:
public async Task<IEnumerable<DTJobAPPOverview>> GetJobOverviewAsync()
...
var efResponsibleUserFullName = await dbContext.tblUsers.Where(u =>
u.UserID == efJob.ResponsibleUserID
).Select(u => u.FullName)
.FirstOrDefaultAsync();
dtJob.ResponsibleUserName = efResponsibleUserName;
var efCase = await dbContext.tblCases.FirstOrDefaultAsync(c =>
c.ID == efJob.FK_CaseID);
dtJob.Case = Mapper.Map<DTCase>(efCase); //Automapper
...
}
顺便说一句,我知道我可能应该将整个应用程序转换为使用导航属性,但这是我们目前拥有的,所以请多多包涵。
【问题讨论】:
-
不要减去
DateTimes 来测量时间间隔——这不是它的用途。Stopwatch是正确的类,它为您提供亚毫秒级的精度,与DateTime.UtcNow不同。另外,您确定DbCommand具有适合您用例的身份吗? -
windows 中的事件正在使用 Timer Tick 并已排队。当计时器滴答发生时,检查异步事件以查看它们是否发生,如果发生,则数据在进程之间传输。因此,您可以在同一个计时器滴答声中处理多个相同的事件。
-
@jdweng 我不确定你在说什么类型的事件,但是异步 I/O 完成肯定不会以任何方式与计时器滴答同步(尽管很明显,回调确实需要一个可能需要一些时间的线程),并且绝对没有一种机制可以检查每个计时器滴答所有异步事件以查看它们是否发生。您可以有数百万个待处理的异步操作,并且在它们真正收到信号之前不会占用一条 CPU 指令。
-
Luaan : 在哪里检查信号?它是检查注册信号状态的定时器事件。
标签: c# datetime asynchronous entity-framework-6 interceptor