LINQ contient insensible à la casse

174

Ce code est sensible à la casse, comment le rendre insensible à la casse?

public IQueryable<FACILITY_ITEM> GetFacilityItemRootByDescription(string description)
{
    return this.ObjectContext.FACILITY_ITEM.Where(fi => fi.DESCRIPTION.Contains(description));
}
Jeaf Gilbert
la source
Les réponses de Sjoerd sont correctes mais ... Je veux obtenir des résultats de recherche pour les noms avec un İ turc (par exemple) lors de l'écriture d'un i et vice versa. Dans ce cas, ToLower semble être la bonne voie à suivre. Corrigez-moi si j'ai tort, s'il-vous plait. A propos du turc ©: en.wikipedia.org/wiki/Dotted_and_dotless_I
He Nrik
@HeNrik - Comme discuté dans Turquie Lien de test dans le commentaire de JYelton sous la réponse acceptée, lorsqu'ils sont exécutés avec la culture turque, ces deux i seront différents - vous ne trouverez donc pas de noms avec l'autre i. Vous voulez ToLowerInvariant. Voir la discussion sous diverses réponses ici .
ToolmakerSteve
c'est une vieille question, mais il convient de noter que dans la version actuelle EF core 2.0 ToLower () fonctionne comme suit person.Where (p => p.Name.ToLower (). Contains (myParam.Name.ToLower () )); J'utilise ceci dans une requête Linq contre une base de données Postgres. Je n'ai pas d'insensibilité à la casse sur le classement des colonnes dans la base de données et j'ai vérifié que sans ToLower (), la correspondance est clairement sensible à la casse.
shelbypereira

Réponses:

72

En supposant que nous travaillons avec des chaînes ici, voici une autre solution "élégante" utilisant IndexOf().

public IQueryable<FACILITY_ITEM> GetFacilityItemRootByDescription(string description)
{
    return this.ObjectContext.FACILITY_ITEM
        .Where(fi => fi.DESCRIPTION
                       .IndexOf(description, StringComparison.OrdinalIgnoreCase) != -1);
}
Jeff Mercado
la source
7
Agréable. Pour mes propres besoins, cela ne fonctionne pas pour LINQ to Entities. Une bonne solution pour LINQ to Objects cependant.
Damian Powell
242
fi => fi.DESCRIPTION.ToLower().Contains(description.ToLower())
Nealv
la source
49
Comme Jon Skeet l'a commenté sur une question connexe , cette méthode ne passera pas le test de la Turquie .
JYelton
5
Non, mais les bases de données fonctionnent à partir de jeux de caractères et de classement. Si vous essayez de pousser le travail vers la base de données, vous devez faire des hypothèses sur le jeu de caractères et le classement, non?
Christopher Stevenson
66
Contient doit utiliser l' IEqualityComparer<string>attribut pour gérer le fonctionnement de la comparaison. Utiliser ToLower et ToUpper pour vérifier l'égalité est une mauvaise idée. Essayez: .Contains(description, StringComparer.CurrentCultureIgnoreCase)par exemple
Dorival
19
Le commentaire de @Dorival ne fonctionne pas, car il donne ce message d'erreur:Error 1 'string' does not contain a definition for 'Contains' and the best extension method overload 'System.Linq.ParallelEnumerable.Contains<TSource>(System.Linq.ParallelQuery<TSource>, TSource, System.Collections.Generic.IEqualityComparer<TSource>)' has some invalid arguments
eMi
6
Containswith StringComparerne reçoit pas de chaîne comme paramètre, ce sera donc une erreur de construction. IndexOfsur Queryablene peut probablement pas être traduit en SQL. Personnellement, j'ai trouvé cette réponse tout à fait valable car nous parlons de LINQ to database.
Thariq Nugrohotomo
122

Si la requête LINQ est exécutée dans le contexte de la base de données, un appel à Contains()est mappé à l' LIKEopérateur:

.Where(a => a.Field.Contains("hello")) devient Field LIKE '%hello%'. L' LIKEopérateur est insensible à la casse par défaut, mais cela peut être modifié en modifiant le classement de la colonne .

Si la requête LINQ est exécutée dans un contexte .NET, vous pouvez utiliser IndexOf () , mais cette méthode n'est pas prise en charge dans LINQ to SQL.

LINQ to SQL ne prend pas en charge les méthodes qui prennent un CultureInfo comme paramètre, probablement parce qu'il ne peut pas garantir que le serveur SQL gère les cultures de la même manière que .NET. Ce n'est pas tout à fait vrai, car il prend en chargeStartsWith(string, StringComparison) .

Cependant, il ne semble pas prendre en charge une méthode qui évalue LIKEdans LINQ to SQL, et à une comparaison insensible à la casse dans .NET, ce qui rend impossible de faire Contains () insensible à la casse de manière cohérente.

Sjoerd
la source
Juste pour FYI EF 4.3 ne prend pas en charge StartsWith. J'obtiens: LINQ to Entities ne reconnaît pas la méthode `` Boolean StartsWith (System.String, System.StringComparison) ''
nakhli
StartWith se convertit en LIKE 'hello%'?
Bart Calixto
Le lien clicdata est mort.
Adam Parkin
2
grand effort pour creuser dans le comportement SQL généré et la base de données pour la clause LIKE
Thariq Nugrohotomo
1
Alors, quelles sont les options lors de l'utilisation d'EF? Dans un contexte, je dois faire une insensitiverecherche de cas , et dans l'autre, j'en ai besoin case sensitive. Dois-je juste prendre le coup de performance et utiliser «toLower ()»?
Zapnologica
12

La réponse acceptée ici ne mentionne pas le fait que si vous avez une chaîne nulle, ToLower () lèvera une exception. La manière la plus sûre serait de faire:

fi => (fi.DESCRIPTION ?? string.Empty).ToLower().Contains((description ?? string.Empty).ToLower())
Marko
la source
Vous ne pouvez pas générer d'exception sur une requête traduite en SQL
Alex Zhukovskiy
@AlexZhukovskiy En quoi cela est-il même pertinent pour ce problème? Si fi.DESCRIPTION est nul ou si description est nul, vous obtenez une exception de référence C # null. Peu importe ce en quoi la requête LINQ convertit du côté SQL. Voici la preuve: dotnetfiddle.net/5pZ1dY
Marko
Parce que cette requête échouera la traduction en SQL car elle ne prend pas en charge l'opérateur de fusion nul. Et vous interrogez probablement la base de données au lieu de charger toutes les entrées pour utiliser la fusion nulle côté client. Donc, si vous l'utilisez - c'est ok du côté client mais échoue sur DB, sinon vous êtes d'accord avec DB et vous ne vous souciez pas de nullref du côté client car cela ne se produira pas parce que C # n'exécute pas cette requête et ne le fait pas ne lit pas réellement les objets nuls.
Alex Zhukovskiy
Cette réponse m'a aidé à résoudre un problème que j'obtenais sur LINQ to Entities où je faisais .IndexOf et .Contains sur un IEnumerable où la valeur de chaîne provenant de la base de données était nulle. Je n'obtenais pas l'erreur jusqu'à ce que le résultat ait été énuméré, puis j'ai reçu un message d'erreur indiquant que «La référence d'objet n'est pas définie sur une instance d'un objet». Je ne pouvais pas comprendre pourquoi cela se produisait avant de voir ce message. Merci!
randyh22
7

En utilisant C # 6.0 (qui autorise les fonctions de corps d'expression et la propagation nulle), pour LINQ to Objects, cela peut être fait en une seule ligne comme celle-ci (en vérifiant également la valeur null):

public static bool ContainsInsensitive(this string str, string value) => str?.IndexOf(value, StringComparison.OrdinalIgnoreCase) >= 0;
Alexei
la source
Cela ne fonctionne pas car ContainsInsensitive n'est pas une commande de magasin
Sven
@Sven - oui, cela ne fonctionne que pour LINQ to Objects. J'ai fixé ma réponse. Merci.
Alexei
4

IndexOf fonctionne mieux dans ce cas

return this
   .ObjectContext
   .FACILITY_ITEM
   .Where(fi => fi.DESCRIPTION.IndexOf(description, StringComparison.OrdinalIgnoreCase)>=0);
Menelaos Vergis
la source
3

Vous pouvez utiliser une chaîne.

    lst.Where(x => string.Compare(x,"valueToCompare",StringComparison.InvariantCultureIgnoreCase)==0);

si vous voulez juste vérifier contient, utilisez "Any"

  lst.Any(x => string.Compare(x,"valueToCompare",StringComparison.InvariantCultureIgnoreCase)==0)
Code d'abord
la source
Cela ne répond pas à la question. L'OP demande «Contient» dans une chaîne (c'est-à-dire qu'une chaîne en contient une autre), pas si une collection de chaînes contient une seule chaîne.
andrewf
1
public static bool Contains(this string input, string findMe, StringComparison comparisonType)
{
    return String.IsNullOrWhiteSpace(input) ? false : input.IndexOf(findMe, comparisonType) > -1;
}
E Rolnicki
la source
2
pouvons-nous utiliser des méthodes d'extension personnalisées dans les requêtes linq? êtes-vous sûr ?
Vishal Sharma
0

Honnêtement, cela n'a pas besoin d'être difficile. Cela peut sembler au début, mais ce n'est pas le cas. Voici une simple requête linq en C # qui fait exactement comme demandé.

Dans mon exemple, je travaille sur une liste de personnes qui ont une propriété appelée FirstName.

var results = ClientsRepository().Where(c => c.FirstName.ToLower().Contains(searchText.ToLower())).ToList();

Cela recherchera dans la base de données une recherche en minuscules mais retournera des résultats complets.

Frank Thomas
la source
-2

Utilisez la méthode String.Equals

public IQueryable<FACILITY_ITEM> GetFacilityItemRootByDescription(string description)
{
    return this.ObjectContext.FACILITY_ITEM
           .Where(fi => fi.DESCRIPTION
           .Equals(description, StringComparison.OrdinalIgnoreCase));
}
Francesco Moroni
la source