Porównanie ciągów bez rozróżniania wielkości liter w LINQ-to-SQL

137

Czytałem, że nierozsądne jest używanie ToUpper i ToLower do porównywania ciągów bez uwzględniania wielkości liter, ale nie widzę alternatywy, jeśli chodzi o LINQ-to-SQL. Argumenty ignoreCase i CompareOptions String.Compare są ignorowane przez LINQ-to-SQL (jeśli używasz bazy danych z uwzględnieniem wielkości liter, otrzymasz porównanie z rozróżnianiem wielkości liter, nawet jeśli poprosisz o porównanie bez uwzględniania wielkości liter). Czy ToLower lub ToUpper jest tutaj najlepszą opcją? Czy jeden jest lepszy od drugiego? Myślałem, że gdzieś czytałem, że ToUpper jest lepsze, ale nie wiem, czy to dotyczy tutaj. (Robię wiele recenzji kodu i wszyscy używają ToLower.)

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

Przekłada się to na zapytanie SQL, które po prostu porównuje parametr row.Name z wartością „test” i nie zwraca wartości „Test” ani „TEST” w bazie danych uwzględniającej wielkość liter.

BlueMonkMN
źródło
1
Dzięki! To naprawdę uratowało mi dzisiaj tyłek. Uwaga: działa z innymi rozszerzeniami LINQ, podobnie jak LINQQuery.Contains("VaLuE", StringComparer.CurrentCultureIgnoreCase)i LINQQuery.Except(new string[]{"A VaLUE","AnOTher VaLUE"}, StringComparer.CurrentCultureIgnoreCase). Wahoo!
Greg Bray,
Zabawne, właśnie przeczytałem, że ToUpper był lepszy w porównaniach z tego źródła: msdn.microsoft.com/en-us/library/dd465121
malckier

Odpowiedzi:

110

Jak powiedziałeś, istnieją pewne ważne różnice między ToUpper i ToLower, a tylko jedna jest niezawodna, gdy próbujesz sprawdzić równość bez rozróżniania wielkości liter.

Najlepszym sposobem sprawdzenia równości bez uwzględniania wielkości liter byłoby :

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

UWAGA, JEDNAK, że to nie działa w tym przypadku! Dlatego utknęliśmy z ToUpperlub ToLower.

Zwróć uwagę na porządkową IgnoreCase, aby była bezpieczna. Ale dokładnie rodzaj sprawdzania (nie) wrażliwego na wielkość liter, którego używasz, zależy od twoich celów. Ale ogólnie rzecz biorąc, użyj Equals do sprawdzania równości i porównaj podczas sortowania, a następnie wybierz odpowiedni StringComparison dla zadania.

Michael Kaplan (uznany autorytet w dziedzinie kultury i postępowania z postaciami, takiego jak ten), ma odpowiednie posty w ToUpper vs.ToLower:

Mówi „String.ToUpper - użyj ToUpper zamiast ToLower i określ InvariantCulture, aby wybrać reguły wielkości liter systemu operacyjnego

Andrew Arnott
źródło
1
Wygląda na to, że nie dotyczy to SQL Server: print upper ('Große Straße') zwraca GROßE STRAßE
BlueMonkMN
1
Również podany przykładowy kod ma ten sam problem, co kod, który podałem, pod względem rozróżniania wielkości liter, gdy jest uruchamiany za pośrednictwem LINQ-to-SQL w bazie danych MS SQL 2005.
BlueMonkMN
2
Zgadzam się. Przepraszam, że nie było jasne. Przykładowy kod, który podałem, nie działa z Linq2Sql, jak wskazałeś w swoim pierwotnym pytaniu. Po prostu powtórzyłem, że sposób, w jaki zacząłeś, był świetną drogą - gdyby tylko zadziałał w tym scenariuszu. I tak, kolejną mydelniczką Mike'a Kaplana jest to, że obsługa znaków SQL Server jest wszędzie. Jeśli potrzebujesz rozróżniania wielkości liter i nie możesz tego uzyskać w żaden inny sposób, sugerowałem (niewyraźnie), abyś zapisywał dane jako wielkie litery, a następnie odpytuj je jako wielkie litery.
Andrew Arnott
3
Cóż, jeśli masz bazę danych uwzględniającą wielkość liter i przechowujesz dane z mieszanymi literami i wyszukujesz dużymi literami, nie otrzymasz dopasowań. Jeśli w wyszukiwaniu dodasz zarówno dane, jak i zapytanie, konwertujesz cały wyszukiwany tekst dla każdego zapytania, które nie jest wydajne.
Andrew Arnott
1
@BlueMonkMN, czy na pewno wkleiłeś poprawne fragmenty? Aż trudno uwierzyć, że serwer MSSQL woli bardziej czerwony niż czarny.
greenoldman
75

Użyłem System.Data.Linq.SqlClient.SqlMethods.Like(row.Name, "test") w moim zapytaniu.

Wykonuje porównanie bez rozróżniania wielkości liter.

Andrew Davey
źródło
3
ha! Używam linq 2 sql od kilku lat, ale do tej pory nie widziałem SqlMethods, dzięki!
Carl Hörberg
3
Znakomity! Przydałoby się jednak więcej szczegółów. Czy to jedno z oczekiwanych zastosowań Like? Czy są możliwe dane wejściowe, które spowodowałyby fałszywie dodatni wynik? Albo fałszywie ujemny wynik? Dokumentacja na temat tej metody jest brak, gdzie jest dokumentacja, która będzie opisywać działanie metody podoba?
Zadanie
2
Myślę, że polega to po prostu na tym, jak SQL Server porównuje ciągi, co prawdopodobnie jest gdzieś konfigurowalne.
Andrew Davey
11
System.Data.Linq.SqlClient.SqlMethods.Like (row.Name, "test") jest tym samym, co row.Name.Contains ("test"). Jak mówi Andrew, zależy to od sortowania serwera sql. Tak więc Like (lub zawiera) nie zawsze przeprowadza porównanie bez rozróżniania wielkości liter.
doekman
3
Pamiętaj, że to sprawia, że ​​kod jest zbyt dopasowany SqlClient.
Jaider
5

Wypróbowałem to używając wyrażenia Lambda i zadziałało.

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

vinahr
źródło
18
Dzieje się tak, ponieważ używasz a List<>, co oznacza, że ​​porównanie odbywa się w pamięci (kod C #), a nie IQueryable(lub ObjectQuery), które wykonałoby porównanie w bazie danych .
drzaus
1
Co powiedział @drzaus. Ta odpowiedź jest po prostu błędna, biorąc pod uwagę, że kontekstem jest linq2sql, a nie zwykły linq.
rsenna
0

Jeśli przekażesz ciąg, który nie rozróżnia wielkości liter do LINQ-to-SQL, zostanie on przekazany do kodu SQL bez zmian, a porównanie nastąpi w bazie danych. Jeśli chcesz wykonywać porównania ciągów bez uwzględniania wielkości liter w bazie danych, wszystko, co musisz zrobić, to utworzyć wyrażenie lambda, które wykonuje porównanie, a dostawca LINQ-to-SQL przetłumaczy to wyrażenie na zapytanie SQL z nienaruszonym ciągiem.

Na przykład to zapytanie LINQ:

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

zostaje przetłumaczony na następujący kod SQL przez dostawcę 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]"

Jak widać, parametr string zostanie porównany w SQL, co oznacza, że ​​wszystko powinno działać tak, jak byś tego oczekiwał.

Andrew Hare
źródło
Nie rozumiem, co mówisz. 1) Same ciągi znaków nie mogą uwzględniać wielkości liter ani wielkości liter w .NET, więc nie mogę przekazać „ciągu znaków bez uwzględniania wielkości liter”. 2) Kwerenda LINQ jest w zasadzie wyrażeniem lambda iw ten sposób przekazuję moje dwa ciągi, więc nie ma to dla mnie żadnego sensu.
BlueMonkMN
3
Chcę wykonać porównanie bez rozróżniania wielkości liter w bazie danych uwzględniającej wielkość liter.
BlueMonkMN
Jakiej bazy danych uwzględniającej wielkość liter używasz?
Andrew Hare
Ponadto zapytanie LINQ nie jest wyrażeniem lambda. Zapytanie LINQ składa się z kilku części (w szczególności operatorów zapytań i wyrażeń lambda).
Andrew Hare
Ta odpowiedź nie ma sensu, ponieważ komentuje BlueMonkMN.
Alf
0

Aby wykonywać zapytania Linq to Sql z rozróżnianiem wielkości liter, należy zadeklarować w polach typu „string”, aby uwzględniana była wielkość liter, określając typ danych serwera za pomocą jednego z poniższych;

varchar(4000) COLLATE SQL_Latin1_General_CP1_CS_AS 

lub

nvarchar(Max) COLLATE SQL_Latin1_General_CP1_CS_AS

Uwaga: „CS” w powyższych typach sortowania oznacza „Z uwzględnieniem wielkości liter”.

Można to wprowadzić w polu „Typ danych serwera” podczas przeglądania właściwości w programie Visual Studio DBML Designer.

Więcej informacji można znaleźć pod adresem http://yourdotnetdesignteam.blogspot.com/2010/06/case-sensitive-linq-to-sql-queries.html

John Hansen
źródło
To jest problem. Zwykle w polu, którego używam, rozróżniana jest wielkość liter (wzór chemiczny CO [tlenek węgla] różni się od Co [kobalt]). Jednak w określonej sytuacji (wyszukiwanie) chcę, aby co pasowało zarówno do Co, jak i CO. Definiowanie dodatkowej właściwości z innym „typem danych serwera” jest niedozwolone (linq do sql dopuszcza tylko jedną właściwość na kolumnę sql). Więc nadal nie ma wyjścia.
doekman
Ponadto, jeśli przeprowadzasz testy jednostkowe, to podejście prawdopodobnie nie będzie zgodne z makietą danych. W akceptowanej odpowiedzi najlepiej zastosować podejście linq / lambda.
Derrick,
0
where row.name.StartsWith(q, true, System.Globalization.CultureInfo.CurrentCulture)
Julio Silveira
źródło
1
Jaki jest tekst SQL, na który jest to tłumaczone, i co pozwala na to, aby nie rozróżniał wielkości liter w środowisku SQL, które w przeciwnym razie traktowałoby go jako rozróżniającą wielkość liter?
BlueMonkMN
0

U mnie działa następujące dwuetapowe podejście (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
źródło
1
Ten kod zawiera błąd, jeśli tekst zaczyna się od tekstu wyszukiwania (powinien być> = 0)
Flatliner DOA
@FlatlinerDOA powinno tak być, != -1ponieważ IndexOf „zwraca wartość -1, jeśli znak lub ciąg nie zostanie znaleziony”
drzaus
0

Czasami wartość przechowywana w bazie danych może zawierać spacje, więc uruchomienie tego może się nie powieść

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

Rozwiązaniem tego problemu jest usunięcie miejsca, a następnie przekonwertowanie jego obudowy, a następnie wybranie w ten sposób

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

Uwaga w tym przypadku

nazwa_własna to wartość pasująca do wartości bazy danych

UsersTBs to klasa

tytuł to kolumna Baza danych

TAHA SULTAN TEMURI
źródło
-1

Pamiętaj, że istnieje różnica między tym, czy zapytanie działa, a wydajnością ! Instrukcja LINQ jest konwertowana na T-SQL, gdy celem instrukcji jest SQL Server, więc musisz pomyśleć o T-SQL, który zostanie utworzony.

Użycie String.Equals najprawdopodobniej (zgaduję) spowoduje przywrócenie wszystkich wierszy z SQL Server, a następnie wykonanie porównania w .NET, ponieważ jest to wyrażenie .NET, którego nie można przetłumaczyć na T-SQL.

Innymi słowy, użycie wyrażenia zwiększy dostęp do danych i usunie możliwość korzystania z indeksów. Sprawdzi się na małych stolikach i nie zauważysz różnicy. Na dużym stole mogłaby się bardzo źle spisać.

To jeden z problemów występujących w LINQ; ludzie nie myślą już o tym, jak spełnią się wypowiedziane przez nich wypowiedzi.

W tym przypadku nie ma sposobu na zrobienie tego, co chcesz, bez użycia wyrażenia - nawet w T-SQL. Dlatego możesz nie być w stanie zrobić tego wydajniej. Nawet odpowiedź T-SQL podana powyżej (przy użyciu zmiennych z sortowaniem) najprawdopodobniej spowoduje zignorowanie indeksów, ale jeśli jest to duża tabela, warto uruchomić instrukcję i spojrzeć na plan wykonania, aby sprawdzić, czy został użyty indeks .

Andrew H.
źródło
2
To nieprawda (nie powoduje zwrócenia wierszy do klienta). Użyłem String.Equals i powodem, dla którego nie działa, jest to, że jest konwertowany na porównanie ciągów TSQL, którego zachowanie zależy od sortowania bazy danych lub serwera. Na przykład zastanawiam się, jak każde napisane przeze mnie wyrażenie LINQ to SQL zostanie przekonwertowane na TSQL. Drogą do tego, co chcę, jest użycie ToUpper, aby zmusić wygenerowany TSQL do użycia UPPER. Wtedy cała logika konwersji i porównania jest nadal wykonywana w TSQL, więc nie tracisz dużo wydajności.
BlueMonkMN,