Confronto tra stringhe senza distinzione tra maiuscole e minuscole in LINQ-to-SQL


137

Ho letto che non è saggio usare ToUpper e ToLower per eseguire confronti di stringhe senza distinzione tra maiuscole e minuscole, ma non vedo alternative quando si tratta di LINQ-to-SQL. Gli argomenti ignoreCase e CompareOptions di String.Compare vengono ignorati da LINQ-to-SQL (se si utilizza un database con distinzione tra maiuscole e minuscole, si ottiene un confronto con distinzione tra maiuscole e minuscole anche se si richiede un confronto senza distinzione tra maiuscole e minuscole). ToLower o ToUpper è l'opzione migliore qui? Uno è migliore dell'altro? Pensavo di aver letto da qualche parte che ToUpper era migliore, ma non so se questo vale qui. (Sto facendo molte revisioni del codice e tutti usano ToLower.)

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

Questo si traduce in una query SQL che confronta semplicemente row.Name con "test" e non restituirà "Test" e "TEST" su un database sensibile al maiuscolo / minuscolo.


1
Grazie! Questo mi ha davvero salvato il culo oggi. Nota: funziona anche con altre estensioni LINQ come LINQQuery.Contains("VaLuE", StringComparer.CurrentCultureIgnoreCase)e LINQQuery.Except(new string[]{"A VaLUE","AnOTher VaLUE"}, StringComparer.CurrentCultureIgnoreCase). Wahoo!
Greg Bray,

Divertente, avevo appena letto che ToUpper era meglio in confronti di questa fonte: msdn.microsoft.com/en-us/library/dd465121
malckier

Risposte:


110

Come dici tu, ci sono alcune differenze importanti tra ToUpper e ToLower, e solo una è affidabile in modo affidabile quando stai provando a fare controlli di uguaglianza insensibili al caso.

Idealmente, il modo migliore per effettuare un controllo di uguaglianza senza distinzione tra maiuscole e minuscole sarebbe :

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

NOTA, TUTTAVIA che in questo caso non funziona ! Pertanto siamo bloccati con ToUppero ToLower.

Si noti l' ordinale IgnoreCase per renderlo di sicurezza di sicurezza. Ma esattamente il tipo di controllo case (in) sensibile che usi dipende da quali sono i tuoi scopi. Ma, in generale, utilizzare Equals per i controlli di uguaglianza e Compare durante l'ordinamento, quindi selezionare StringComparison giusto per il lavoro.

Michael Kaplan (un'autorità riconosciuta in materia di cultura e gestione dei personaggi come questo) ha post pertinenti su ToUpper vs. ToLower:

Dice "String.ToUpper - Usa ToUpper anziché ToLower e specifica InvariantCulture per raccogliere le regole di alloggiamento del sistema operativo "


1
Sembra che questo non si applichi a SQL Server: print upper ('Große Straße') restituisce GROßE STRAßE
BlueMonkMN

1
Inoltre, il codice di esempio fornito presenta lo stesso problema del codice fornito per quanto riguarda la distinzione tra maiuscole e minuscole quando eseguito tramite LINQ-to-SQL su un database MS SQL 2005.
BlueMonkMN

2
Sono d'accordo. Mi dispiace non ero chiaro. Il codice di esempio che ho fornito non funziona con Linq2Sql come hai sottolineato nella domanda originale. Stavo semplicemente ribadendo che il modo in cui hai iniziato era un ottimo modo di procedere, se avesse funzionato solo in questo scenario. E sì, un altro soapbox di Mike Kaplan è che la gestione dei personaggi di SQL Server è ovunque. Se hai bisogno di distinzione tra maiuscole e minuscole e non puoi ottenerlo in altro modo, stavo suggerendo (in modo poco chiaro) che tu memorizzi i dati come maiuscoli e quindi li interroghi in maiuscolo.
Andrew Arnott,

3
Bene, se hai un database con distinzione tra maiuscole e minuscole e memorizzi in maiuscole e minuscole e cerchi in maiuscolo, non otterrai corrispondenze. Se metti in rilievo sia i dati che la query nella tua ricerca, stai convertendo tutto il testo che stai cercando per ogni query, che non è performante.
Andrew Arnott,

1
@BlueMonkMN, sei sicuro di aver incollato gli snippet corretti? È difficile credere che MSSQL Server preferisca il rosso più del nero.
Greenoldman,

75

Ho usato System.Data.Linq.SqlClient.SqlMethods.Like(row.Name, "test") nella mia query.

Ciò esegue un confronto senza distinzione tra maiuscole e minuscole.


3
ah! uso linq 2 sql ormai da diversi anni ma fino ad ora non avevo visto SqlMethods, grazie!
Carl Hörberg,

3
Brillante! Potrebbe usare più dettagli, però. Questo è uno degli usi previsti di Like? Ci sono possibili input che potrebbero causare un risultato falso positivo? O un risultato falso negativo? La documentazione su questo metodo è carente, dov'è la documentazione che sarà descrivere il funzionamento del metodo Come?
Compito

2
Penso che si basi solo su come SQL Server confronta le stringhe, che è probabilmente configurabile da qualche parte.
Andrew Davey,

11
System.Data.Linq.SqlClient.SqlMethods.Like (row.Name, "test") è uguale a row.Name.Contains ("test"). Come dice Andrew, questo dipende dalle regole di confronto del server sql. Quindi Like (o contiene) non sempre esegue un confronto senza distinzione tra maiuscole e minuscole.
doekman,

3
Attenzione, questo rende il codice troppo accoppiato SqlClient.
Jaider,

5

L'ho provato usando l'espressione Lambda e ha funzionato.

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


18
Questo perché stai usando un List<>, il che significa che il confronto ha luogo in memoria (codice C #) piuttosto che un IQueryable(o ObjectQuery) che eseguirà il confronto nel database .
drzaus,

1
Cosa ha detto @drzaus. Questa risposta è semplicemente sbagliata, considerando che il contesto è linq2sql e non linq normale.
rsenna,

0

Se si passa una stringa senza distinzione tra maiuscole e minuscole in LINQ-to-SQL, questa verrà passata invariata in SQL e il confronto avverrà nel database. Se si desidera eseguire confronti di stringhe senza distinzione tra maiuscole e minuscole nel database, è sufficiente creare un'espressione lambda che faccia il confronto e il provider LINQ-SQL tradurrà quell'espressione in una query SQL con la stringa intatta.

Ad esempio questa query LINQ:

from user in Users
where user.Email == "foo@bar.com"
select user

viene tradotto nel seguente SQL dal provider 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 "foo@bar.com"

Come puoi vedere, il parametro stringa verrà confrontato in SQL, il che significa che le cose dovrebbero funzionare esattamente come ti aspetteresti.


Non capisco cosa stai dicendo. 1) Le stringhe stesse non possono fare distinzione tra maiuscole e minuscole o maiuscole e minuscole in .NET, quindi non posso passare una "stringa senza distinzione tra maiuscole e minuscole". 2) Una query LINQ fondamentalmente È un'espressione lambda, ed è così che sto passando le mie due stringhe, quindi questo non ha alcun senso per me.
BlueMonkMN

3
Voglio eseguire un confronto CASE-INSENSITIVE su un database CASE-SENSITIVE.
BlueMonkMN

Quale database CASE-SENSITIVE stai usando?
Andrew Hare,

Inoltre, una query LINQ non è un'espressione lambda. Una query LINQ è composta da più parti (in particolare operatori di query ed espressioni lambda).
Andrew Hare,

Questa risposta non ha senso come commenta BlueMonkMN.
Alf,

0

Per eseguire le query da Linq a SQL con distinzione tra maiuscole e minuscole, dichiarare i campi 'stringa' con distinzione tra maiuscole e minuscole specificando il tipo di dati del server utilizzando uno dei seguenti;

varchar(4000) COLLATE SQL_Latin1_General_CP1_CS_AS 

o

nvarchar(Max) COLLATE SQL_Latin1_General_CP1_CS_AS

Nota: "CS" nei tipi di regole di confronto precedenti significa "Case sensitive".

Questo può essere inserito nel campo "Tipo di dati del server" quando si visualizza una proprietà utilizzando Visual Studio DBML Designer.

Per maggiori dettagli consultare http://yourdotnetdesignteam.blogspot.com/2010/06/case-sensitive-linq-to-sql-queries.html


Questo è il problema. Normalmente il campo che utilizzo è case sensitive (la formula chimica CO [monossido di carbonio] è diversa da Co [cobalto]). Tuttavia, in una situazione specifica (ricerca) voglio che co corrisponda sia a Co che a CO. Definire una proprietà aggiuntiva con un "tipo di dati server" diverso non è legale (linq a sql consente solo una proprietà per colonna sql). Quindi ancora niente.
doekman,

Inoltre, se si esegue il test unitario, questo approccio non sarà probabilmente compatibile con una simulazione di dati. Meglio usare l'approccio linq / lambda nella risposta accettata.
Derrick,

0
where row.name.StartsWith(q, true, System.Globalization.CultureInfo.CurrentCulture)

1
Qual è il testo SQL in cui questo viene tradotto e cosa gli consente di distinguere tra maiuscole e minuscole in un ambiente SQL che altrimenti lo considererebbe sensibile al maiuscolo / minuscolo?
BlueMonkMN il

0

Il seguente approccio in 2 fasi funziona per me (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);
}

1
Questo codice presenta un bug se il testo inizia con il testo di ricerca (dovrebbe essere> = 0)
Flatliner DOA

@FlatlinerDOA in realtà dovrebbe essere != -1perché IndexOf "restituisce -1 se il carattere o la stringa non viene trovato"
drzaus,

0

A volte il valore archiviato nel database può contenere spazi, pertanto l'esecuzione potrebbe non riuscire

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

La soluzione a questi problemi è quella di rimuovere lo spazio, quindi convertire il suo caso e selezionare in questo modo

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

Nota in questo caso

customname è un valore da abbinare al valore del database

UsersTBs è di classe

il titolo è la colonna del database


-1

Ricorda che c'è una differenza tra se la query funziona e se funziona in modo efficiente ! Un'istruzione LINQ viene convertita in T-SQL quando la destinazione dell'istruzione è SQL Server, quindi è necessario pensare al T-SQL che verrebbe prodotto.

L'uso di String.Equals molto probabilmente (suppongo) riporterà tutte le righe da SQL Server e quindi farà il confronto in .NET, poiché è un'espressione .NET che non può essere tradotta in T-SQL.

In altre parole, l'uso di un'espressione aumenterà l'accesso ai dati e rimuoverà la capacità di utilizzare gli indici. Funzionerà su piccoli tavoli e non noterai la differenza. Su un grande tavolo potrebbe funzionare molto male.

Questo è uno dei problemi che esiste con LINQ; la gente non pensa più a come le dichiarazioni che scriveranno saranno soddisfatte.

In questo caso non c'è modo di fare ciò che vuoi senza usare un'espressione, nemmeno in T-SQL. Pertanto potresti non essere in grado di farlo in modo più efficiente. Anche la risposta T-SQL fornita sopra (utilizzando variabili con regole di confronto) molto probabilmente comporterà l'ignoramento degli indici, ma se si tratta di una tabella di grandi dimensioni vale la pena eseguire l'istruzione e guardare il piano di esecuzione per vedere se è stato utilizzato un indice .


2
Questo non è vero (non causa la restituzione delle righe al client). Ho usato String.Equals e il motivo per cui non funziona è perché viene convertito in un confronto di stringhe TSQL, il cui comportamento dipende dalle regole di confronto del database o del server. Io per primo penso a come ogni espressione LINQ to SQL che scrivo verrebbe convertita in TSQL. Il modo per quello che voglio è usare ToUpper per forzare il TSQL generato ad usare UPPER. Quindi tutta la logica di conversione e confronto viene ancora eseguita in TSQL in modo da non perdere molte prestazioni.
BlueMonkMN
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.