Comparaison de chaînes insensible à la casse dans LINQ-to-SQL

137

J'ai lu qu'il n'était pas judicieux d'utiliser ToUpper et ToLower pour effectuer des comparaisons de chaînes insensibles à la casse, mais je ne vois aucune alternative en ce qui concerne LINQ-to-SQL. Les arguments ignoreCase et CompareOptions de String.Compare sont ignorés par LINQ-to-SQL (si vous utilisez une base de données sensible à la casse, vous obtenez une comparaison sensible à la casse même si vous demandez une comparaison insensible à la casse). ToLower ou ToUpper est-il la meilleure option ici? Est-ce que l'un est meilleur que l'autre? Je pensais avoir lu quelque part que ToUpper était meilleur, mais je ne sais pas si cela s'applique ici. (Je fais beaucoup de révisions de code et tout le monde utilise ToLower.)

Dim s = From row In context.Table Where String.Compare(row.Name, "test", StringComparison.InvariantCultureIgnoreCase) = 0

Cela se traduit par une requête SQL qui compare simplement row.Name avec "test" et ne renverra pas "Test" et "TEST" sur une base de données sensible à la casse.

BlueMonkMN
la source
1
Merci! Cela m'a vraiment sauvé le cul aujourd'hui. Remarque: cela fonctionne également avec d'autres extensions LINQ comme LINQQuery.Contains("VaLuE", StringComparer.CurrentCultureIgnoreCase)et LINQQuery.Except(new string[]{"A VaLUE","AnOTher VaLUE"}, StringComparer.CurrentCultureIgnoreCase). Wahoo!
Greg Bray
Drôle, je viens de lire que ToUpper était meilleur dans les comparaisons de cette source: msdn.microsoft.com/en-us/library/dd465121
malckier

Réponses:

110

Comme vous le dites, il existe des différences importantes entre ToUpper et ToLower, et une seule est fiable lorsque vous essayez de faire des vérifications d'égalité insensibles à la casse.

Idéalement, la meilleure façon de faire une vérification d'égalité insensible à la casse serait :

String.Equals(row.Name, "test", StringComparison.OrdinalIgnoreCase)

NOTEZ CEPENDANT que cela ne fonctionne pas dans ce cas! Par conséquent, nous sommes coincés avec ToUpperou ToLower.

Notez l' ordinal IgnoreCase pour le rendre sûr. Mais exactement le type de vérification sensible à la casse que vous utilisez dépend de vos objectifs. Mais en général, utilisez Equals pour les vérifications d'égalité et Compare lorsque vous triez, puis choisissez le StringComparison approprié pour le travail.

Michael Kaplan (une autorité reconnue sur la culture et la gestion des personnages comme celle-ci) a publié des articles pertinents sur ToUpper vs ToLower:

Il dit "String.ToUpper - Utilisez ToUpper plutôt que ToLower, et spécifiez InvariantCulture afin de récupérer les règles de casse du système d'exploitation "

Andrew Arnott
la source
1
Il semble que cela ne s'applique pas à SQL Server: print upper ('Große Straße') renvoie GROßE STRAßE
BlueMonkMN
1
En outre, l'exemple de code que vous avez fourni présente le même problème que le code que j'ai fourni dans la mesure où il est sensible à la casse lorsqu'il est exécuté via LINQ-to-SQL sur une base de données MS SQL 2005.
BlueMonkMN
2
Je suis d'accord. Désolé, je n'étais pas clair. L'exemple de code que j'ai fourni ne fonctionne pas avec Linq2Sql comme vous l'avez signalé dans votre question d'origine. Je répétais simplement que la façon dont vous avez commencé était une excellente façon de procéder - si cela ne fonctionnait que dans ce scénario. Et oui, une autre boîte à savon de Mike Kaplan est que la gestion des caractères de SQL Server est partout. Si vous avez besoin d'insensible à la casse et que vous ne pouvez pas l'obtenir autrement, je suggérais (de manière peu claire) de stocker les données en majuscules, puis de les interroger en majuscules.
Andrew Arnott
3
Eh bien, si vous avez une base de données sensible à la casse et que vous stockez en casse mixte et recherchez en majuscules, vous n'obtiendrez pas de correspondance. Si vous valorisez à la fois les données et la requête dans votre recherche, vous convertissez tout le texte que vous recherchez pour chaque requête, ce qui n'est pas performant.
Andrew Arnott
1
@BlueMonkMN, êtes-vous sûr d'avoir collé les bons extraits? Il est difficile de croire que MSSQL Server préfère le rouge au noir.
greenoldman
75

J'ai utilisé System.Data.Linq.SqlClient.SqlMethods.Like(row.Name, "test") dans ma requête.

Cela effectue une comparaison insensible à la casse.

Andrew Davey
la source
3
Ha! J'utilise linq 2 sql depuis plusieurs années maintenant mais je n'avais pas vu SqlMethods jusqu'à présent, merci!
Carl Hörberg
3
Brillant! Pourrait utiliser plus de détails, cependant. Est-ce l'une des utilisations attendues de Like? Existe-t-il des entrées possibles qui provoqueraient un faux résultat positif? Ou un faux résultat négatif? La documentation sur cette méthode fait défaut, où est la documentation qui va décrire le fonctionnement de la méthode Comme?
Tâche du
2
Je pense que cela dépend simplement de la façon dont SQL Server compare les chaînes, ce qui est probablement configurable quelque part.
Andrew Davey
11
System.Data.Linq.SqlClient.SqlMethods.Like (row.Name, "test") est identique à row.Name.Contains ("test"). Comme Andrew le dit, cela dépend du classement du serveur SQL. Ainsi, Like (ou contient) n'effectue pas toujours une comparaison insensible à la casse.
doekman
3
Attention, cela rend le code trop couplé SqlClient.
Jaider
5

J'ai essayé cela en utilisant l'expression Lambda, et cela a fonctionné.

List<MyList>.Any (x => (String.Equals(x.Name, name, StringComparison.OrdinalIgnoreCase)) && (x.Type == qbType) );

vinahr
la source
18
C'est parce que vous utilisez a List<>, ce qui signifie que la comparaison a lieu en mémoire (code C #) plutôt qu'un IQueryable(ou ObjectQuery) qui effectuerait la comparaison dans la base de données .
drzaus
1
Ce que @drzaus a dit. Cette réponse est tout simplement fausse, étant donné que le contexte est linq2sql, et non linq régulier.
rsenna le
0

Si vous passez une chaîne insensible à la casse dans LINQ-to-SQL, elle sera transmise au SQL sans modification et la comparaison se produira dans la base de données. Si vous souhaitez effectuer des comparaisons de chaînes insensibles à la casse dans la base de données, tout ce que vous avez à faire est de créer une expression lambda qui effectue la comparaison et le fournisseur LINQ-to-SQL traduira cette expression en une requête SQL avec votre chaîne intacte.

Par exemple cette requête LINQ:

from user in Users
where user.Email == "[email protected]"
select user

est traduit en SQL suivant par le fournisseur LINQ-to-SQL:

SELECT [t0].[Email]
FROM [User] AS [t0]
WHERE [t0].[Email] = @p0
-- note that "@p0" is defined as nvarchar(11)
-- and is passed my value of "[email protected]"

Comme vous pouvez le voir, le paramètre de chaîne sera comparé en SQL, ce qui signifie que les choses devraient fonctionner exactement comme vous vous y attendez.

Andrew Hare
la source
Je ne comprends pas ce que vous dites. 1) Les chaînes elles-mêmes ne peuvent pas être insensibles à la casse ou à la casse dans .NET, donc je ne peux pas passer une "chaîne insensible à la casse". 2) Une requête LINQ EST essentiellement une expression lambda, et c'est ainsi que je passe mes deux chaînes, donc cela n'a aucun sens pour moi.
BlueMonkMN
3
Je souhaite effectuer une comparaison CASE-INSENSITIVE sur une base de données CASE-SENSITIVE.
BlueMonkMN
Quelle base de données CASE-SENSITIVE utilisez-vous?
Andrew Hare
En outre, une requête LINQ n'est pas une expression lambda. Une requête LINQ est composée de plusieurs parties (notamment les opérateurs de requête et les expressions lambda).
Andrew Hare
Cette réponse n'a pas de sens comme le commente BlueMonkMN.
Alf le
0

Pour effectuer des requêtes Linq à Sql sensibles à la casse, déclarez que les champs «chaîne» sont sensibles à la casse en spécifiant le type de données du serveur à l'aide de l'un des éléments suivants;

varchar(4000) COLLATE SQL_Latin1_General_CP1_CS_AS 

ou

nvarchar(Max) COLLATE SQL_Latin1_General_CP1_CS_AS

Remarque: Le «CS» dans les types de classement ci-dessus signifie «Sensible à la casse».

Cela peut être entré dans le champ «Type de données du serveur» lors de l'affichage d'une propriété à l'aide de Visual Studio DBML Designer.

Pour plus de détails, voir http://yourdotnetdesignteam.blogspot.com/2010/06/case-sensitive-linq-to-sql-queries.html

John Hansen
la source
Voilà le problème. Normalement, le champ que j'utilise est sensible à la casse (la formule chimique CO [monoxyde de carbone] est différente de Co [cobalt]). Cependant, dans une situation spécifique (recherche), je veux que co corresponde à la fois à Co et à CO. La définition d'une propriété supplémentaire avec un "type de données serveur" différent n'est pas légale (linq to sql n'autorise qu'une propriété par colonne sql). Donc toujours pas aller.
doekman
De plus, si vous effectuez des tests unitaires, cette approche ne sera probablement pas compatible avec une simulation de données. Il est préférable d'utiliser l'approche linq / lambda dans la réponse acceptée.
Derrick
0
where row.name.StartsWith(q, true, System.Globalization.CultureInfo.CurrentCulture)
Julio Silveira
la source
1
Quel est le texte SQL dans lequel cela est traduit et qu'est-ce qui lui permet d'être insensible à la casse dans un environnement SQL qui, autrement, le traiterait comme sensible à la casse?
BlueMonkMN
0

L'approche en 2 étapes suivante fonctionne pour moi (VS2010, ASP.NET MVC3, SQL Server 2008, Linq to SQL):

result = entRepos.FindAllEntities()
    .Where(e => e.EntitySearchText.Contains(item));

if (caseSensitive)
{
    result = result
        .Where(e => e.EntitySearchText.IndexOf(item, System.StringComparison.CurrentCulture) >= 0);
}
Jim Davies
la source
1
Ce code a un bug si le texte commence par le texte de recherche (devrait être> = 0)
Flatliner DOA
@FlatlinerDOA cela devrait en fait être != -1parce que IndexOf "renvoie -1 si le caractère ou la chaîne n'est pas trouvé"
drzaus
0

Parfois, la valeur stockée dans la base de données peut contenir des espaces, l'exécution de cette opération peut donc échouer

String.Equals(row.Name, "test", StringComparison.OrdinalIgnoreCase)

La solution à ce problème est de supprimer de l'espace puis de convertir son boîtier puis de sélectionner comme ceci

 return db.UsersTBs.Where(x => x.title.ToString().ToLower().Replace(" ",string.Empty).Equals(customname.ToLower())).FirstOrDefault();

Notez dans ce cas

customname correspond à la valeur de la base de données

UsersTBs est classe

title est la colonne Database

TAHA SULTAN TEMURI
la source
-1

N'oubliez pas qu'il y a une différence entre si la requête fonctionne et si elle fonctionne efficacement ! Une instruction LINQ est convertie en T-SQL lorsque la cible de l'instruction est SQL Server, vous devez donc penser au T-SQL qui serait produit.

L'utilisation de String.Equals ramènera très probablement (je suppose) toutes les lignes de SQL Server, puis effectuera la comparaison dans .NET, car il s'agit d'une expression .NET qui ne peut pas être traduite en T-SQL.

En d'autres termes, l'utilisation d'une expression augmentera votre accès aux données et supprimera votre capacité à utiliser des index. Cela fonctionnera sur de petites tables et vous ne remarquerez pas la différence. Sur une grande table, cela pourrait très mal fonctionner.

C'est l'un des problèmes qui existe avec LINQ; les gens ne pensent plus à la manière dont les déclarations qu'ils écrivent seront remplies.

Dans ce cas, il n'y a pas moyen de faire ce que vous voulez sans utiliser une expression - même pas en T-SQL. Par conséquent, vous ne pourrez peut-être pas le faire plus efficacement. Même la réponse T-SQL donnée ci-dessus (en utilisant des variables avec classement) entraînera très probablement l'ignorance des index, mais s'il s'agit d'une grande table, il vaut la peine d'exécuter l'instruction et de regarder le plan d'exécution pour voir si un index a été utilisé. .

Andrew H
la source
2
Ce n'est pas vrai (cela ne provoque pas le retour des lignes au client). J'ai utilisé String.Equals et la raison pour laquelle cela ne fonctionne pas est qu'il est converti en une comparaison de chaînes TSQL, dont le comportement dépend du classement de la base de données ou du serveur. Pour ma part, je considère comment chaque expression LINQ to SQL que j'écris serait convertie en TSQL. Le moyen d'atteindre ce que je veux est d'utiliser ToUpper pour forcer le TSQL généré à utiliser UPPER. Ensuite, toute la logique de conversion et de comparaison est toujours effectuée dans TSQL afin que vous ne perdiez pas beaucoup de performances.
BlueMonkMN