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.StartsWith
, String.EndsWith
和String.Contains
为LIKE
而不是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