Comparação entre cadeias sem distinção entre maiúsculas e minúsculas no LINQ-to-SQL

137

Li que não é aconselhável usar o ToUpper e o ToLower para realizar comparações de seqüências que não diferenciam maiúsculas de minúsculas, mas não vejo alternativa quando se trata de LINQ-to-SQL. Os argumentos ignoreCase e CompareOptions de String.Compare são ignorados pelo LINQ-to-SQL (se você estiver usando um banco de dados com distinção entre maiúsculas e minúsculas, você obtém uma comparação com distinção entre maiúsculas e minúsculas, mesmo que solicite uma comparação sem distinção entre maiúsculas e minúsculas). ToLower ou ToUpper é a melhor opção aqui? Um é melhor que o outro? Pensei ter lido em algum lugar que o ToUpper era melhor, mas não sei se isso se aplica aqui. (Estou fazendo muitas revisões de código e todo mundo está usando o ToLower.)

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

Isso se traduz em uma consulta SQL que simplesmente compara row.Name com "test" e não retornará "Test" e "TEST" em um banco de dados com distinção entre maiúsculas e minúsculas.

BlueMonkMN
fonte
1
Obrigado! Isso realmente salvou minha bunda hoje. Nota: ele funciona com outras extensões LINQ como LINQQuery.Contains("VaLuE", StringComparer.CurrentCultureIgnoreCase)e LINQQuery.Except(new string[]{"A VaLUE","AnOTher VaLUE"}, StringComparer.CurrentCultureIgnoreCase). Wahoo!
Greg Bray
Engraçado, eu tinha lido apenas que ToUpper foi melhor em comparações de fonte: msdn.microsoft.com/en-us/library/dd465121
malckier

Respostas:

110

Como você diz, existem algumas diferenças importantes entre o ToUpper e o ToLower, e apenas uma é confiável quando você está tentando fazer verificações de igualdade sem distinção entre maiúsculas e minúsculas.

Idealmente, a melhor maneira de fazer uma verificação de igualdade que não diferencia maiúsculas de minúsculas seria :

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

NOTA, contudo, que isso não funciona neste caso! Portanto, estamos presos a ToUpperou ToLower.

Observe o Ordinal IgnoreCase para torná-lo seguro. Mas exatamente o tipo de verificação sensível a maiúsculas e minúsculas que você usa depende de quais são seus objetivos. Mas, em geral, use Equals para verificações de igualdade e Compare ao classificar e, em seguida, escolha a StringComparison correta para o trabalho.

Michael Kaplan (uma autoridade reconhecida em cultura e manipulação de personagens como essa) tem posts relevantes no ToUpper vs. ToLower:

Ele diz "String.ToUpper - use ToUpper em vez de ToLower e especifique InvariantCulture para obter as regras de uso do sistema operacional "

Andrew Arnott
fonte
1
Parece que isso não se aplica ao SQL Server: print upper ('Große Straße') retorna GROßE STRAßE
BlueMonkMN
1
Além disso, o código de exemplo que você forneceu tem o mesmo problema que o código que forneci, tanto quanto faz distinção entre maiúsculas e minúsculas, quando executado via LINQ-to-SQL em um banco de dados MS SQL 2005.
BlueMonkMN
2
Concordo. Desculpe, eu não estava claro. O código de exemplo que forneci não funciona com o Linq2Sql, como você apontou na sua pergunta original. Eu estava apenas reafirmando que a maneira como você começou foi um ótimo caminho a percorrer - se funcionasse apenas nesse cenário. E sim, outra caixa de sabão de Mike Kaplan é que o manuseio de caracteres do SQL Server está em todo lugar. Se você precisar de distinção entre maiúsculas e minúsculas e não puder obtê-lo de outra maneira, sugeri (claro) que você armazene os dados em maiúsculas e, em seguida, os consulte em maiúsculas.
237 Andrew Arnott
3
Bem, se você tem um banco de dados que diferencia maiúsculas de minúsculas e armazena em maiúsculas e minúsculas e procura em maiúsculas, não receberá correspondências. Se você atualizar os dados e a consulta em sua pesquisa, estará convertendo todo o texto que está pesquisando para cada consulta, o que não é de alto desempenho.
1313 Andrew Arnott
1
@BlueMonkMN, tem certeza de que colou os snippets corretos? É difícil acreditar que o MSSQL Server prefere o vermelho mais do que o preto.
greenoldman
75

Eu usei System.Data.Linq.SqlClient.SqlMethods.Like(row.Name, "test") na minha consulta.

Isso realiza uma comparação que não diferencia maiúsculas de minúsculas.

Andrew Davey
fonte
3
ha! usa linq 2 sql há vários anos, mas não tinha visto SqlMethods até agora, obrigado!
Carl Hörberg 19/02/10
3
Brilhante! Poderia usar mais detalhes, no entanto. Este é um dos usos esperados do Like? Existem entradas possíveis que causariam um resultado falso positivo? Ou um resultado falso negativo? A documentação sobre este método está faltando, onde está a documentação que irá descrever o funcionamento do método Like?
Task
2
Eu acho que depende apenas de como o SQL Server compara as strings, o que provavelmente é configurável em algum lugar.
Andrew Davey
11
System.Data.Linq.SqlClient.SqlMethods.Like (row.Name, "test") é o mesmo que row.Name.Contains ("test"). Como Andrew está dizendo, isso depende do agrupamento do servidor sql. Assim, o Like (ou contém) nem sempre realiza uma comparação que não diferencia maiúsculas de minúsculas.
Dokman 28/06
3
Esteja ciente, isso torna o código muito par SqlClient.
213 Jaider
5

Eu tentei isso usando a expressão Lambda, e funcionou.

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

vinahr
fonte
18
Isso List<>ocorre porque você está usando a , o que significa que a comparação ocorre na memória (código C #), em vez de uma IQueryable(ou ObjectQuery) que faria a comparação no banco de dados .
drzaus
1
O que @drzaus disse. Esta resposta está simplesmente errada, considerando que o contexto é linq2sql e não linq regular.
rsenna
0

Se você passar uma string que não diferencia maiúsculas de minúsculas para o LINQ-to-SQL, ela será passada para o SQL inalterada e a comparação ocorrerá no banco de dados. Se você deseja fazer comparações de strings sem distinção entre maiúsculas e minúsculas no banco de dados, tudo o que você precisa fazer é criar uma expressão lambda que faça a comparação e o provedor LINQ-to-SQL converterá essa expressão em uma consulta SQL com a string intacta.

Por exemplo, esta consulta LINQ:

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

é traduzido para o seguinte SQL pelo provedor LINQ-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]"

Como você pode ver, o parâmetro string será comparado no SQL, o que significa que as coisas devem funcionar da maneira que você espera.

Andrew Hare
fonte
Eu não entendo o que você está dizendo. 1) As strings em si não podem diferenciar maiúsculas de minúsculas ou maiúsculas de minúsculas no .NET; portanto, não posso transmitir uma "string que não diferencia maiúsculas de minúsculas". 2) Uma consulta LINQ é basicamente uma expressão lambda, e é assim que estou passando minhas duas strings, para que isso não faça sentido para mim.
BlueMonkMN
3
Desejo executar uma comparação CASE-INSENSITIVE em um banco de dados CASE-SENSITIVE.
BlueMonkMN
Qual banco de dados SENSÍVEL EM CASO você está usando?
8119 Andrew Hare
Além disso, uma consulta LINQ não é uma expressão lambda. Uma consulta LINQ é composta por várias partes (principalmente operadores de consulta e expressões lambda).
8119 Andrew Hare
Esta resposta não faz sentido, como comenta o BlueMonkMN.
Alf
0

Para executar consultas Linq a Sql com distinção entre maiúsculas e minúsculas, declare que os campos 'string' diferenciam maiúsculas de minúsculas, especificando o tipo de dados do servidor usando um dos seguintes;

varchar(4000) COLLATE SQL_Latin1_General_CP1_CS_AS 

ou

nvarchar(Max) COLLATE SQL_Latin1_General_CP1_CS_AS

Nota: O 'CS' nos tipos de agrupamento acima significa 'Distinção entre maiúsculas e minúsculas'.

Isso pode ser inserido no campo "Tipo de dados do servidor" ao exibir uma propriedade usando o Visual Studio DBML Designer.

Para obter mais detalhes, consulte http://yourdotnetdesignteam.blogspot.com/2010/06/case-sensitive-linq-to-sql-queries.html

John Hansen
fonte
Essa é a questão. Normalmente, o campo que eu uso diferencia maiúsculas de minúsculas (a fórmula química CO [monóxido de carbono] é diferente de Co [cobalto]). No entanto, em uma situação específica (pesquisa), quero que co corresponda a Co e CO. Definir uma propriedade adicional com um "tipo de dados do servidor" diferente não é legal (o linq to sql permite apenas uma propriedade por coluna sql). Então ainda não vai.
Dokman 28/06
Além disso, se estiver realizando testes de unidade, essa abordagem provavelmente não será compilável com uma simulação de dados. É melhor usar a abordagem linq / lambda na resposta aceita.
Derrick
0
where row.name.StartsWith(q, true, System.Globalization.CultureInfo.CurrentCulture)
Julio Silveira
fonte
1
Qual é o texto SQL no qual isso é traduzido e o que permite que não faça distinção entre maiúsculas e minúsculas em um ambiente SQL que, de outra forma, trataria como diferencia maiúsculas de minúsculas?
BlueMonkMN
0

A seguinte abordagem em duas etapas funciona para mim (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
fonte
1
Este código tem um bug se o texto começa com o texto de pesquisa (deve ser> = 0)
Flatliner DOA
@FlatlinerDOA, na verdade, deve ser != -1porque IndexOf "retorna -1 se o caractere ou a string não for encontrado"
drzaus
0

Às vezes, o valor armazenado no banco de dados pode conter espaços, portanto, a execução pode falhar

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

A solução para esses problemas é remover espaço, converter sua caixa e selecionar assim

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

Observe neste caso

nome personalizado é o valor que corresponde ao valor do banco de dados

UsersTBs é classe

title é a coluna Banco de Dados

TAHA SULTAN TEMURI
fonte
-1

Lembre-se de que existe uma diferença entre se a consulta funciona e se funciona com eficiência ! Uma instrução LINQ é convertida em T-SQL quando o destino da instrução é SQL Server, portanto, é necessário pensar no T-SQL que seria produzido.

É provável que o uso de String.Equals (suponho) traga de volta todas as linhas do SQL Server e faça a comparação no .NET, porque é uma expressão do .NET que não pode ser convertida em T-SQL.

Em outras palavras, o uso de uma expressão aumentará seu acesso a dados e removerá sua capacidade de usar índices. Funcionará em pequenas mesas e você não notará a diferença. Em uma mesa grande, poderia ter um desempenho muito ruim.

Esse é um dos problemas que existem com o LINQ; as pessoas não pensam mais em como as declarações que escrevem serão cumpridas.

Nesse caso, não há como fazer o que você deseja sem usar uma expressão - nem mesmo no T-SQL. Portanto, talvez você não consiga fazer isso com mais eficiência. Mesmo a resposta T-SQL fornecida acima (usando variáveis ​​com intercalação) provavelmente resultará em índices sendo ignorados, mas se for uma tabela grande, vale a pena executar a instrução e examinar o plano de execução para verificar se um índice foi usado. .

Andrew H
fonte
2
Isso não é verdade (não faz com que as linhas retornem ao cliente). Eu usei String.Equals e o motivo pelo qual não funciona é porque é convertido em uma comparação de strings TSQL, cujo comportamento depende do agrupamento do banco de dados ou servidor. Eu, pelo menos, considero como todas as expressões de LINQ to SQL que eu escrevo seriam convertidas em TSQL. O caminho para o que eu quero é usar o ToUpper para forçar o TSQL gerado a usar o UPPER. Então, toda a lógica de conversão e comparação ainda é feita no TSQL, para que você não perca muito desempenho.
BlueMonkMN