EntityFramework生成的SQL StartsWith()包含计划改变ESCAPE’〜’(代字号)

使用EntityFramework,子句.OrderBy(x => x.Title.StartsWith("foo"))导致SQL WHERE (Title LIKE 'foo%' ESCAPE '~') .OrderBy(x => x.Title.StartsWith("foo")) WHERE (Title LIKE 'foo%' ESCAPE '~')

查看完整查询的执行计划,我发现当我删除ESCAPE '~'时,我得到了一个不同的计划(一个使用列的非聚集索引)。

为什么EF试图逃避不需要它的字符串,我怎么能让它停止?

多余的ESCAPE当然可以改变基数估计并给出不同的计划。 虽然有趣,但我发现它在这个测试中更准确而不是更少!

 CREATE TABLE T ( Title VARCHAR(50), ID INT IDENTITY, Filler char(1) NULL, UNIQUE NONCLUSTERED (Title, ID) ) INSERT INTO T (Title) SELECT TOP 1000 CASE WHEN ROW_NUMBER() OVER (ORDER BY @@SPID) < 10 THEN 'food' ELSE LEFT(NEWID(), 10) END FROM master..spt_values 

没有Escape

 SELECT * FROM T WHERE (Title LIKE 'foo%') 

在此处输入图像描述

随着Escape

 SELECT * FROM T WHERE (Title LIKE 'foo%' ESCAPE '~') 

在此处输入图像描述

如果没有升级到更新版本的EF或编写自己的自定义DbProviderManifest实现,我认为您在尝试删除ESCAPE运气不佳。

String.StartsWithString.EndsWithString.ContainsLIKE而不是CHARINDEX是EF 4.0中的新function

看一下System.Data.Entity, Version=4.0.0.0的定义System.Data.Entity, Version=4.0.0.0在reflection器中的相关函数似乎是(在System.Data.SqlClient.SqlProviderManifest

 public override string EscapeLikeArgument(string argument) { bool flag; EntityUtil.CheckArgumentNull(argument, "argument"); return EscapeLikeText(argument, true, out flag); } 

该方法的签名是

 internal static string EscapeLikeText(string text, bool alwaysEscapeEscapeChar, out bool usedEscapeChar) { usedEscapeChar = false; if (((!text.Contains("%") && !text.Contains("_")) && (!text.Contains("[") && !text.Contains("^"))) && (!alwaysEscapeEscapeChar || !text.Contains("~"))) { return text; } StringBuilder builder = new StringBuilder(text.Length); foreach (char ch in text) { switch (ch) { case '%': case '_': case '[': case '^': case '~': builder.Append('~'); usedEscapeChar = true; break; } builder.Append(ch); } return builder.ToString(); } 

所以只是硬编码总是使用escape并且忽略返回的标志。

所以EF的版本只是将ESCAPE '~'附加到所有LIKE查询。

这似乎是最近代码库中已经改进的东西。

SqlFunctionCallHandler.TranslateConstantParameterForLike的定义是

 //  // Function to translate the StartsWith, EndsWith and Contains canonical functions to LIKE expression in T-SQL // and also add the trailing ESCAPE '~' when escaping of the search string for the LIKE expression has occurred //  private static void TranslateConstantParameterForLike( SqlGenerator sqlgen, DbExpression targetExpression, DbConstantExpression constSearchParamExpression, SqlBuilder result, bool insertPercentStart, bool insertPercentEnd) { result.Append(targetExpression.Accept(sqlgen)); result.Append(" LIKE "); // If it's a DbConstantExpression then escape the search parameter if necessary. bool escapingOccurred; var searchParamBuilder = new StringBuilder(); if (insertPercentStart) { searchParamBuilder.Append("%"); } searchParamBuilder.Append( SqlProviderManifest.EscapeLikeText(constSearchParamExpression.Value as string, false, out escapingOccurred)); if (insertPercentEnd) { searchParamBuilder.Append("%"); } var escapedSearchParamExpression = constSearchParamExpression.ResultType.Constant(searchParamBuilder.ToString()); result.Append(escapedSearchParamExpression.Accept(sqlgen)); // If escaping did occur (special characters were found), then append the escape character used. if (escapingOccurred) { result.Append(" ESCAPE '" + SqlProviderManifest.LikeEscapeChar + "'"); } } 

SqlProviderManifest.EscapeLikeText与已显示的代码相同。 请注意,它现在将false作为第二个参数传递,并使用输出参数标志仅在必要时附加ESCAPE

从Entity Framework 6.2开始,作为DbFunctions一部分,还增加了对DbFunctions .Like()DbFunctions

所以现在你可以这样做:

 var query = db.People.Where(p => DbFunctions.Like(p.Name, "w%")); 

有关更多信息: https : //github.com/aspnet/EntityFramework6/issues/241