SQL généré par EntityFramework StartsWith () contient un plan qui modifie ESCAPE '~' (tilde)

En utilisant EntityFramework, la clause .OrderBy(x => x.Title.StartsWith("foo")) donne le SQL WHERE (Title LIKE 'foo%' ESCAPE '~') .

En regardant le plan d'exécution pour la requête complète, je vois que j'obtiens un plan différent (l'un utilisant l'index non groupé de la colonne) quand je supprime l' ESCAPE '~' .

Pourquoi EF tente-t-il d'échapper à une string qui n'en a pas besoin et comment puis-je l'arrêter?

L' ESCAPE superflue peut certainement modifier les estimations de cardinalité et donner un plan différent. Bien que curieusement, je l'ai trouvé plus précis que less dans ce 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 

Sans Escape

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

entrez la description de l'image ici

Avec Escape

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

entrez la description de l'image ici

DbProviderManifest de mettre à jour vers une version plus récente d'EF ou d'écrire votre propre implémentation personnalisée de DbProviderManifest Je pense que vous n'avez pas de chance dans votre tentative de suppression d' ESCAPE .

Traduire Ssortingng.StartsWith , Ssortingng.EndsWith et Ssortingng.Contains à LIKE plutôt que CHARINDEX était nouveau dans EF 4.0

En regardant la définition de System.Data.Entity, Version=4.0.0.0 dans le réflecteur, la fonction concernée semble être (dans System.Data.SqlClient.SqlProviderManifest )

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

La signature pour cette méthode est

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

Il est donc juste codé en dur de toujours utiliser escape et le drapeau renvoyé est ignoré.

Donc, cette version d'EF ajoute simplement le ESCAPE '~' à toutes les requêtes LIKE .

Cela semble être quelque chose qui a été amélioré dans la base de code la plus récente.

La définition de SqlFunctionCallHandler.TranslateConstantParameterForLike est

 // <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 ssortingng 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 SsortingngBuilder(); if (insertPercentStart) { searchParamBuilder.Append("%"); } searchParamBuilder.Append( SqlProviderManifest.EscapeLikeText(constSearchParamExpression.Value as ssortingng, false, out escapingOccurred)); if (insertPercentEnd) { searchParamBuilder.Append("%"); } var escapedSearchParamExpression = constSearchParamExpression.ResultType.Constant(searchParamBuilder.ToSsortingng()); 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 est le même code que celui déjà montré. Notez qu'il passe maintenant false tant que deuxième paramètre et utilise l'indicateur de paramètre de sortie pour append uniquement l' ESCAPE si nécessaire.

À partir de Entity Framework 6.2, il y a le support supplémentaire pour .Like() dans le cadre de DbFunctions .

Alors maintenant vous pouvez faire ceci:

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

Pour plus d'informations: https://github.com/aspnet/EntityFramework6/issues/241