如何将EF生成的SQL查询跟踪回创建它的代码

时间:2015-03-10 14:29:02

标签: c# sql-server entity-framework

我遇到了一些问题,我们只在生产中看到一些运行缓慢的查询,我可以在分析器中看到效果不佳的SQL,但是,我不知道如何使用它来追溯到首先生成语句的代码,或者甚至可以追溯到EF查询。 EF是否有能力识别SQL语句的来源以帮助追踪代码中的问题?

我认为这个问题可能与代码加载悲观相关,即它加载整个结果集然后过滤代码中的列表而不是在SQL中过滤它

3 个答案:

答案 0 :(得分:1)

您可以在Repository或DataContext(执行查询的常见位置)中执行以下操作来调试每个查询的写入。

 var data= from item in entity
                 where item.id = 564564
                 select item;    
 Debug.WriteLine(((System.Data.Objects.ObjectQuery)data).ToTraceString());

您可以编写以下代码来说明执行上述查询时的调用堆栈是什么。然后找到您要查找的查询,调用堆栈将告诉您执行查询的位置。

StackTrace stackTrace = new StackTrace();           // get call stack
StackFrame[] stackFrames = stackTrace.GetFrames();

您可以使用microsoft tracing或log4net记录这些内容,然后轻松找到您的查询。

答案 1 :(得分:0)

您可以在数据库中创建另一个实体(即:DebugEntity),并在实际查询之前/之后对其运行查询。

ctx.DebugEntity.Where(x => x.ID == "myId1"); //myId2, myId3, myId4, whatever helps you locate the LINQ query from the profiler...

这应该出现在SQL分析器中。

答案 2 :(得分:0)

旧线程,但是您可以实现一个DbCommandInterceptor来构建堆栈跟踪并将其作为注释添加到SQL命令中。这样会将调用的C#函数与事件探查器和Azure中的EF SQL耦合。

应该执行以下操作:

public class QueryOriginInterceptor : IDbCommandInterceptor
{
    private const string _sqlCommentToken = "--";
    private const string stackLoggerStartTag = _sqlCommentToken + " Stack:";

    private bool _shouldLog = false;

    public static string StackLoggerStartTag => stackLoggerStartTag;

    public QueryOriginInterceptor(bool shouldLog = true)
    {
        _shouldLog = shouldLog;
    }

    void AppendStackTraceToSqlCommand(DbCommand command)
    {
        if (!_shouldLog)
            return;

        int positionOfExistingCommentStartTag = command.CommandText.IndexOf(stackLoggerStartTag);

        if (positionOfExistingCommentStartTag < 0)
        {
            IEnumerable<string> frames = (new StackTrace())
                .GetFrames()
                .ToList()
                .Select(f => $"{f?.GetMethod()?.ReflectedType?.FullName ?? "[unknown]"}.{f?.GetMethod()?.Name}")
                .Where(l => !l.StartsWith("System.") && !l.StartsWith(this.GetType().FullName));

            string comment = $"{stackLoggerStartTag}{Environment.NewLine}{_sqlCommentToken} {string.Join($"{Environment.NewLine}{_sqlCommentToken} ", frames)}{Environment.NewLine}";
            command.CommandText = $"{Environment.NewLine}{comment}{command.CommandText}";
        }
    }

    void IDbCommandInterceptor.ReaderExecuting(DbCommand command, DbCommandInterceptionContext<DbDataReader> interceptionContext) => AppendStackTraceToSqlCommand(command);

    void IDbCommandInterceptor.NonQueryExecuting(DbCommand command, DbCommandInterceptionContext<int> interceptionContext) => AppendStackTraceToSqlCommand(command);

    void IDbCommandInterceptor.ScalarExecuting(DbCommand command, DbCommandInterceptionContext<object> interceptionContext) => AppendStackTraceToSqlCommand(command);

    void IDbCommandInterceptor.NonQueryExecuted(DbCommand command, DbCommandInterceptionContext<int> interceptionContext) { }

    void IDbCommandInterceptor.ReaderExecuted(DbCommand command, DbCommandInterceptionContext<DbDataReader> interceptionContext) { }

    void IDbCommandInterceptor.ScalarExecuted(DbCommand command, DbCommandInterceptionContext<object> interceptionContext) { }
}

然后在DbContext构造函数中,将其添加为拦截器:

DbInterception.Add(new QueryOriginInterceptor());

在事件探查器中,您将看到正在执行的查询,如下所示:

-- Stack:
-- YourApp.Users.GetUser
SELECT
    [Project1].[ID] AS [ID],
    FROM [dbo].[User]
WHERE [Extend1].[ID] = @p__linq__0

这种方法有一些考虑因素,例如构建堆栈跟踪的性能下降,如果从不同位置调用相同的函数,则可能会缓存多个执行计划。