SQL generato da EntityFramework StartsWith () contiene il piano che modifica ESCAPE ‘~’ (tilde)

Usando EntityFramework, la clausola .OrderBy(x => x.Title.StartsWith("foo")) risulta in SQL WHERE (Title LIKE 'foo%' ESCAPE '~') .

Osservando il piano di esecuzione per la query completa, vedo che ottengo un piano diverso (uno che utilizza l’indice non cluster della colonna) quando rimuovo ESCAPE '~' .

Perché EF cerca di sfuggire a una stringa che non ne ha bisogno e come posso fermarla?

Il superfluo ESCAPE può certamente alterare le stime di cardinalità e dare un piano diverso. Anche se stranamente ho trovato renderlo più preciso piuttosto che meno in questo test!

 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 

Senza Escape

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

inserisci la descrizione dell'immagine qui

Con Escape

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

inserisci la descrizione dell'immagine qui

A parte l'aggiornamento a una versione più recente di EF o la creazione di un'implementazione personalizzata di DbProviderManifest penso che tu sia sfortunato nel tentativo di rimuovere ESCAPE .

Tradurre String.StartsWith , String.EndsWith e String.Contains su LIKE invece che CHARINDEX era nuovo in EF 4.0

Guardando la definizione di System.Data.Entity, Version=4.0.0.0 nel reflector, la funzione pertinente sembra essere (in System.Data.SqlClient.SqlProviderManifest )

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

La firma per quel metodo è

 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(); } 

Quindi è solo hardcoded per usare sempre l'escape e il flag che viene restituito viene ignorato.

Quindi la versione di EF aggiunge semplicemente ESCAPE '~' a tutte le query LIKE .

Questo sembra essere qualcosa che è stato migliorato nella più recente base di codice.

La definizione di 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 è lo stesso codice già mostrato. Si noti che ora passa false come secondo parametro e utilizza il flag del parametro di output per aggiungere solo ESCAPE ove necessario.

A partire da Entity Framework 6.2, esiste il supporto aggiunto per .Like() come parte di DbFunctions .

Quindi ora puoi fare questo:

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

Per maggiori informazioni: https://github.com/aspnet/EntityFramework6/issues/241