调试实体框架查询

发布于 2024-11-08 05:13:26 字数 1797 浏览 0 评论 0原文

这是一个关于具体情况的主观问题。对我来说,这个问题的主要目标是提醒自己编写解决方案。但是,如果已经有解决方案或替代方法,我想知道。

我正在开发一个项目,并使用 Entity Framework 4 进行数据库访问。数据库设计是我无法控制的。该数据库是多年前设计的,在我看来,该数据库设计不适合当前的数据库用途。这会导致非常复杂的查询。

这是我第一次在项目中使用实体框架,但我在针对 MS SQL Server 进行开发方面拥有丰富的经验。

我发现自己一次又一次地做的是:

  • 我编写一个复杂的 L2E 查询。查询速度慢或返回错误结果
  • 我正在查看我的 L2E 查询,我完全不知道如何改进它
  • 我启动 SQL Profiler 并捕获 EF 从我的查询生成的 SQL
  • 我想要执行该 sql 的一部分识别出现问题的查询部分
  • 该查询以 sp_executesql 的形式进行,带有十几个参数,因为如果一个参数在查询中使用 3 次,L2E 将创建 3 个参数并向所有参数传递相同的值。每个参数的处理方式相同。
  • 现在我必须从 sp_executesql 中提取 SQL,取消转义所有转义的撇号,并用其值替换查询中的每个参数。
  • 完成此操作后,我终于可以运行部分查询并查明问题。
  • 我回到我的 L2E 代码,更改它以解决我发现的问题,然后重复这个循环。

老实说,我开始认为,如果您没有自己的数据库设计,那么就不应该使用 ORM。

除此之外,取消转义 sql 并替换参数的过程是我想要自动化的过程。目标是获得可以在 SSMS 中运行的“裸”、去参数化 SQL。

这是一个非常简单的示例,展示了我在个人资料中看到的内容以及我想要得到的结果。我的真实案例要复杂很多倍。

捕获:

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])

我只是要编写代码将第一个转换为第二个,如果没有更好的方法,我将在这里发布解决方案。但也许有人已经完成了?或者也许有一个探查器或其他东西,可以给我可以在 SSMS 中部分执行的 SQL 代码?

This is a bit of subjective question about a specific situation. Main goal for this question for me is to remind my self to code up the solution. However if there is already a solution, or an alternate approach, I would like to know it.

I'm working on a project and I'm using Entity Framework 4 for database access. The database design is something that I don't have control over. The database was designed many years ago, and in my opinion the database design does not fit for the current database purposes. This results in very complicated queries.

This is the first time I'm using Entity Framework in a project, but I have extensive experience in development against MS SQL Server.

What I found myself doing again and again is this:

  • I write a complex L2E query. The query either slow or returns wrong results
  • I'm looking at my L2E query and I have absolutely no idea how to improve it
  • I fire up SQL Profiler and capture the SQL that EF generated from my query
  • I want to execute part of that sql to identify the part of the query that is giving problems
  • The query comes through as sp_executesql with a dozen of parameters, because if a parameter is used 3 times in a query, L2E creates 3 parameters and passes to all of them the same value. Same deal with every parameter.
  • Now I have to extract the SQL from sp_executesql, unescape all escaped apostrophes, and substitute every parameter in the query with its value
  • After this is done I finally can run parts of the query and pin-point the problem.
  • I go back to my L2E code, change it to fix the problem I found and the cycle repeats.

To be honest, I'm starting thinking that one should not use an ORM if you don't own database design.

This aside, the process of unescaping the sql and substituting the parameters is the one that I want to automate. The goal is to get 'naked', de-parametrized sql, that I can run in SSMS.

This a very simple example of what I see in the profile and what I want to get in result. My real cases are many times more complex.

The capture:

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'

Desired result:

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])

I'm just going to write code to convert the likes of first to the likes of second if there is nothing better, I'll post the solution here. But maybe it's already done by someone? Or maybe there is a profiler or something, that can give me sql code I can execute partially in SSMS?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

魔法少女 2024-11-15 05:13:26

这就是我最终得到的结果。一些注意事项:

  • 这不会在 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));
            }
        }
    }
}

So here is what I ended up with. A couple of notes:

  • This won't work in 100% of cases, but this is good enough for me
  • There is a lot to improve in terms of usability. Currently I put a shortcut to the compiled binary on the desktop, cut the text to convert to clipboard, double-click the shortcut and paste the result.
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));
            }
        }
    }
}
心作怪 2024-11-15 05:13:26

嗯,也许这会有所帮助。 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"    

Well,may be this will be helpfull. MSVS 2010 has IntelliTrace. Every time when EF make a query there is an ADO.Net Event with a query

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"    
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文