使用EntityFramework,子句.OrderBy(x => x.Title.StartsWith("foo"))
会生成SQL WHERE (Title LIKE 'foo%' ESCAPE '~')
。
查看完整查询的执行计划,当我删除ESCAPE '~'
时,我看到我得到了一个不同的计划(一个使用列的非聚集索引)。
为什么EF试图逃避不需要它的字符串,如何让它停止?
答案 0 :(得分:11)
多余的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
new in EF 4.0
查看反射器中System.Data.Entity, Version=4.0.0.0
的定义,相关函数似乎是(在System.Data.SqlClient.SqlProviderManifest
中)
public override string EscapeLikeArgument(string argument)
{
bool flag;
EntityUtil.CheckArgumentNull<string>(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的定义是
// <summary>
// 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
// </summary>
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
。
答案 1 :(得分:1)
从Entity Framework 6.2开始,.Like()
的{{1}}增加了对DbFunctions
的支持。
所以现在你可以这样做:
var query = db.People.Where(p => DbFunctions.Like(p.Name, "w%"));
了解更多信息:https://github.com/aspnet/EntityFramework6/issues/241