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

时间:2013-12-10 13:40:23

标签: c# sql .net sql-server entity-framework

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

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

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

2 个答案:

答案 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%')

enter image description here

使用Escape

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

enter image description here

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

String.StartsWithString.EndsWithString.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