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%')
Avec Escape
SELECT * FROM T WHERE (Title LIKE 'foo%' ESCAPE '~')
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