Отладка запросов Entity Framework - PullRequest
6 голосов
/ 19 мая 2011

Это немного субъективный вопрос о конкретной ситуации. Основная цель этого вопроса для меня - напомнить себе о необходимости кодирования решения. Однако, если уже есть решение или альтернативный подход, я хотел бы знать это.

Я работаю над проектом и использую Entity Framework 4 для доступа к базе данных. Дизайн базы данных - это то, что я не могу контролировать. База данных была разработана много лет назад, и, на мой взгляд, дизайн базы данных не подходит для текущих целей базы данных. Это приводит к очень сложным запросам.

Я впервые использую Entity Framework в проекте, но у меня большой опыт разработки под MS SQL Server.

То, что я делал снова и снова, это:

  • Я пишу сложный запрос L2E. Запрос либо медленный, либо возвращает неправильные результаты
  • Я смотрю на свой запрос L2E и совершенно не знаю, как его улучшить
  • Я запускаю SQL Profiler и записываю SQL, сгенерированный EF из моего запроса
  • Я хочу выполнить часть этого sql, чтобы определить часть запроса, которая вызывает проблемы
  • Запрос выполняется как sp_executesql с дюжиной параметров, потому что, если параметр используется 3 раза в запросе, L2E создает 3 параметра и передает всем им одно и то же значение. То же самое касается каждого параметра.
  • Теперь мне нужно извлечь SQL из sp_executesql, удалить все экранированные апострофы и заменить каждый параметр в запросе его значением
  • После того, как это будет сделано, я, наконец, смогу выполнить части запроса и определить проблему.
  • Я возвращаюсь к своему коду L2E, меняю его, чтобы исправить обнаруженную проблему, и цикл повторяется.

Если честно, я начинаю думать, что не следует использовать ORM, если у вас нет собственного дизайна базы данных .

Помимо этого, я хочу автоматизировать процесс удаления SQL-файла и подстановки параметров. Цель состоит в том, чтобы получить «голый», де-параметризованный SQL, который я могу запустить в SSMS.

Это очень простой пример того, что я вижу в профиле и что я хочу получить в результате. Мои реальные дела во много раз сложнее.

Захват:

exec sp_executesql N'SELECT 
[Extent1].[ProductName] AS [ProductName]
FROM  [dbo].[Products] AS [Extent1]
INNER JOIN [dbo].[Categories] AS [Extent2] ON [Extent1].[CategoryID] = [Extent2].[CategoryID]
WHERE ([Extent1].[UnitPrice] > @p__linq__0) AND ([Extent2].[CategoryName] = @p__linq__1) AND (N''Chang'' <> [Extent1].[ProductName])',N'@p__linq__0 decimal(1,0),@p__linq__1 nvarchar(4000)',@p__linq__0=1,@p__linq__1=N'Beverages'

Желаемый результат:

SELECT 
[Extent1].[ProductName] AS [ProductName]
FROM  [dbo].[Products] AS [Extent1]
INNER JOIN [dbo].[Categories] AS [Extent2] ON [Extent1].[CategoryID] = [Extent2].[CategoryID]
WHERE ([Extent1].[UnitPrice] > 1) AND ([Extent2].[CategoryName] = N'Beverages') AND (N'Chang' <> [Extent1].[ProductName])

Я просто собираюсь написать код для преобразования подобий первого в подобия второго, если нет ничего лучше, я выложу решение здесь. Но, может быть, это уже кем-то сделано? Или, может быть, есть профилировщик или что-то, что может дать мне SQL-код, который я могу частично выполнить в SSMS?

Ответы [ 2 ]

5 голосов
/ 23 мая 2011

Итак, вот чем я закончил.Пара замечаний:

  • Это не сработает в 100% случаев, но для меня этого вполне достаточно
  • Существует много улучшений в плане удобства использования.В настоящее время я ставлю ярлык на скомпилированный двоичный файл на рабочем столе, обрезаю текст для преобразования в буфер обмена, дважды щелкаю по ярлыку и вставляю результат.
using System;
using System.Text.RegularExpressions;
using System.Windows.Forms;

namespace EFC
{
    static class Program
    {
        [STAThread]
        static void Main()
        {
            try
            {
                string input = Clipboard.GetText();
                const string header = "exec sp_executesql N'";

                CheckValidInput(input.StartsWith(header), "Input does not start with {0}", header);

                // Find part of the statement that constitutes whatever sp_executesql has to execute
                int bodyStartIndex = header.Length;
                int bodyEndIndex = FindClosingApostroph(input, bodyStartIndex);

                CheckValidInput(bodyEndIndex > 0, "Unable to find closing \"'\" in the body");

                string body = input.Substring(bodyStartIndex, bodyEndIndex - bodyStartIndex);

                // Unescape 's
                body = body.Replace("''", "'");

                // Work out where the paramters are
                int blobEndIndex = FindClosingApostroph(input, bodyEndIndex + 4);
                CheckValidInput(bodyEndIndex > 0, "Unable to find closing \"'\" in the params");

                string ps = input.Substring(blobEndIndex);

                // Reverse, so that P__linq_2 does not get substituted in p__linq_20
                Regex regexEf = new Regex(@"(?<name>@p__linq__(?:\d+))=(?<value>(?:.+?)((?=,@p)|($)))", RegexOptions.RightToLeft);
                Regex regexLinqToSql = new Regex(@"(?<name>@p(?:\d+))=(?<value>(?:.+?)((?=,@p)|($)))", RegexOptions.RightToLeft);

                MatchCollection mcEf = regexEf.Matches(ps);
                MatchCollection mcLinqToSql = regexLinqToSql.Matches(ps);
                MatchCollection mc = mcEf.Count > 0 ? mcEf : mcLinqToSql;

                // substitutes parameters in the statement with their values
                foreach (Match m in mc)
                {
                    string name = m.Groups["name"].Value;
                    string value = m.Groups["value"].Value;
                    body = body.Replace(name, value);
                }

                Clipboard.SetText(body);
                MessageBox.Show("Done!", "CEF");

            }
            catch (ApplicationException ex)
            {
                MessageBox.Show(ex.Message, "Error");
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message, "Error");
                MessageBox.Show(ex.StackTrace, "Error");
            }
        }

        static int FindClosingApostroph(string input, int bodyStartIndex)
        {
            for (int i = bodyStartIndex; i < input.Length; i++)
            {
                if (input[i] == '\'' && i + 1 < input.Length)
                {
                    if (input[i + 1] != '\'')
                    {
                        return i;
                    }
                    i++;
                }
            }

            return -1;
        }

        static void CheckValidInput(bool isValid, string message, params object[] args)
        {
            if (!isValid)
            {
                throw new ApplicationException(string.Format(message, args));
            }
        }
    }
}
2 голосов
/ 19 мая 2011

Ну, может быть, это будет полезно. MSVS 2010 имеет IntelliTrace. Каждый раз, когда EF делает запрос, происходит событие ADO.Net с запросом

Execute Reader "SELECT TOP (1) 
[Extent1].[id] AS [id], 
[Extent1].[Sid] AS [Sid], 
[Extent1].[Queue] AS [Queue], 
[Extent1].[Extension] AS [Extension]
FROM [dbo].[Operators] AS [Extent1]
WHERE [Extent1].[Sid] = @p__linq__0"    Command Text = "SELECT TOP (1) \r\n[Extent1].[id] AS [id], \r\n[Extent1].[Sid] AS [Sid], \r\n[Extent1].[Queue] AS [Queue], \r\n[Extent1].[Extension] AS [Extension]\r\nFROM [dbo].[Operators] AS [Extent1]\r\nWHERE [Extent1].[Sid] = @p__linq__0", 

Connection String = "Data Source=paris;Initial Catalog=telephony;Integrated Security=True;MultipleActiveResultSets=True"    
...