调试实体框架查询

And*_*ykh 6 debugging entity-framework entity-framework-4

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

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

这是我第一次在项目中使用Entity Framework,但我在针对MS SQL Server的开发方面拥有丰富的经验.

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

  • 我写了一个复杂的L2E查询.查询要么慢,要么返回错误的结果
  • 我正在查看我的L2E查询,我完全不知道如何改进它
  • 我启动SQL事件探查器并捕获从我的查询生成的EF
  • 我想执行该部分SQL以识别出问题的查询部分
  • 查询以带有十几个参数的sp_executesql形式出现,因为如果在查询中使用了3次参数,则L2E会创建3个参数并将所有参数传递给相同的值.每个参数都相同.
  • 现在我必须从sp_executesql中提取SQL,unescape所有转义的撇号,并用其值替换查询中的每个参数
  • 完成此操作后,我终于可以运行部分查询并精确定位问题.
  • 我回到我的L2E代码,更改它以解决我发现的问题并重复循环.

说实话,我开始认为如果你不拥有数据库设计就不应该使用ORM.

除此之外,unescaping 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'
Run Code Online (Sandbox Code Playgroud)

期望的结果:

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])
Run Code Online (Sandbox Code Playgroud)

如果没有什么比这更好的话,我只是想编写代码来将第一类转换为第二类,我会在这里发布解决方案.但也许它已经由某人完成了?或者也许有一个分析器或其他东西,可以给我一些我可以在SSMS中部分执行的SQL代码?

And*_*ykh 5

所以这就是我最终的结果.几个笔记:

  • 这在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));
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)