Порівняння рядків, що не враховують регістр, порівнюють у LINQ-SQL


137

Я читав, що нерозумно використовувати ToUpper і ToLower для порівняння рядків, нечутливих до регістру, але не бачу альтернативи, коли мова заходить про LINQ-to-SQL. Аргументи ignoreCase і CompareOptions програми String.Compare ігноруються LINQ-to-SQL (якщо ви використовуєте базу даних, що враховує регістр, ви отримуєте порівняння з урахуванням регістру, навіть якщо просите порівняння, яке не враховує регістр). Тут найкращий варіант - ToLower або ToUpper? Чи один кращий за інший? Я думав, що десь прочитав, що ToUpper краще, але не знаю, чи це стосується тут. (Я роблю багато оглядів коду, і всі користуються ToLower.)

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

Це перекладається на SQL-запит, який просто порівнює row.Name з "test" і не повертає "Test" та "TEST" на залежно від регістру бази даних.


1
Дякую! Це сьогодні врятувало мою дупу. Примітка: це працює з іншими розширеннями LINQ теж , як LINQQuery.Contains("VaLuE", StringComparer.CurrentCultureIgnoreCase)і LINQQuery.Except(new string[]{"A VaLUE","AnOTher VaLUE"}, StringComparer.CurrentCultureIgnoreCase). Ваху!
Грег Брей

Смішно, я щойно прочитав, що ToUpper був кращим у порівнянні з цього джерела: msdn.microsoft.com/en-us/library/dd465121
malckier

Відповіді:


110

Як ви кажете, існують деякі важливі відмінності між ToUpper і ToLower, і лише одна достовірно точна, коли ви намагаєтесь зробити перевірку рівності нечутливих до випадків.

В ідеалі найкращим способом зробити перевірку рівності, що не враховує регістр, було б :

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

Зверніть увагу, ЯКЩО це не працює в цьому випадку! Тому ми застрягли з ToUpperабо ToLower.

Зверніть увагу на звичайне IgnoreCase , щоб зробити його безпеку безпечним. Але саме від типу (не) чутливої ​​перевірки, яку ви використовуєте, залежить від ваших цілей. Але, як правило, використовуйте рівне для перевірки рівності та порівняйте, коли ви сортуєте, а потім виберіть потрібне для роботи завдання StringComp.

Майкл Каплан (визнаний авторитет з питань культури та обробки персонажів, таких як цей) має відповідні пости на ToUpper vs. ToLower:

Він говорить: "String.ToUpper - використовуйте ToUpper, а не ToLower, і вкажіть InvariantCulture для того, щоб підібрати правила обкладинки ОС "


1
Здається, це не стосується SQL Server: верхня версія друку ("Große Straße") повертає GROßE STRAßE
BlueMonkMN

1
Крім того, наданий вами зразок коду має ту саму проблему, що і код, який я надав, оскільки він відрізняється від регістру при запуску через LINQ-SQL у базі даних MS SQL 2005.
BlueMonkMN

2
Я згоден. Вибачте, мені було незрозуміло. Зразок коду, який я надав, не працює з Linq2Sql, як ви вказали у своєму оригінальному запитанні. Я просто повторював, що шлях, який ви почали, - це чудовий шлях - якщо він працював лише за цим сценарієм. І так, ще одна мильниця Mike Kaplan - це те, що обробка персонажів SQL Server є повсюдно. Якщо вам потрібна нечутливість до регістру, і ви не можете її отримати іншим способом, я пропонував (незрозуміло) зберігати дані як великі регістри, а потім запитувати їх як великі регістри.
Ендрю Арнотт

3
Ну, якщо у вас є база даних, що відрізняється від регістру, і ви зберігаєте в змішаному регістрі та шукаєте у верхньому регістрі, ви не отримаєте збігів. Якщо ви підкреслите і дані, і запит у своєму пошуку, ви конвертуєте весь текст, який ви шукаєте, для кожного запиту, який не відповідає результатам.
Ендрю Арнотт

1
@BlueMonkMN, ви впевнені, що вставили правильні фрагменти? Важко повірити, що MSSQL Server вважає за краще червоний, ніж чорний.
greenoldman

75

Я використовував System.Data.Linq.SqlClient.SqlMethods.Like(row.Name, "test") у своєму запиті.

Це виконує порівняння з урахуванням регістру.


3
га! вже кілька років використовую linq 2 sql, але досі не бачив SqlMethods, дякую!
Карл Херберг

3
Блискуче! Хоча можна використати детальніше. Це одне з очікуваних застосувань Like? Чи можливі входи, які можуть спричинити помилковий позитивний результат? Або помилковий негативний результат? У документації за цим методом відсутня, де документація , яка буде описана робота методу Як?
Завдання

2
Я думаю, це просто спирається на те, як SQL Server порівнює рядки, які, можливо, десь можна налаштувати.
Ендрю Дейві

11
System.Data.Linq.SqlClient.SqlMethods.Like (row.Name, "test") - те саме, що row.Name.Contains ("тест"). Як каже Ендрю, це залежить від зібрання сервера sql. Так Like (або містить) не завжди проводить порівняння, що не враховує регістр.
doekman

3
Будьте в курсі, це робить код надто пару SqlClient.
Джайдер

5

Я спробував це, використовуючи лямбдаський вираз, і це спрацювало.

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


18
Це тому, що ви використовуєте a List<>, це означає, що порівняння відбувається в пам'яті (C # код), а не IQueryable(або ObjectQuery), яка б виконувала порівняння в базі даних .
drzaus

1
Що сказав @drzaus Ця відповідь просто неправильна, враховуючи, що контекст є linq2sql, а не регулярний linq.
rsenna

0

Якщо ви передасте рядок, нечутливий до регістру, в LINQ-to-SQL, він буде переданий в SQL без змін, а порівняння відбудеться в базі даних. Якщо ви хочете зробити порівняння рядків, нечутливих до регістру, у базі даних, все, що вам потрібно зробити, - це створити лямбда-вираз, який робить порівняння, і постачальник LINQ-до-SQL переведе це вираз у SQL-запит із недоторканою строкою.

Наприклад, цей LINQ-запит:

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

постачальник LINQ до SQL перекладається на наступний 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"

Як бачите, параметр рядка буде порівнюватися в SQL, що означає, що речі повинні працювати саме так, як ви їх очікували.


Я не розумію, що ти кажеш. 1) Самі рядки не можуть бути чутливими до регістру або чутливими до регістру в .NET, тому я не можу передавати "нечутливу до регістру строку". 2) Запит LINQ в основному - це лямбда-вираз, і саме тому я передаю свої два рядки, тому це не має для мене ніякого сенсу.
BlueMonkMN

3
Я хочу здійснити порівняння ВИПУСКУВАННЯ В СЛУЧАХ на базі даних CASE-SENSITIVE.
BlueMonkMN

Яку базу даних CASE-SENSITIVE ви використовуєте?
Ендрю Заєць

Також запит LINQ - це не лямбда-вираз. Запит LINQ складається з декількох частин (найбільш чітких операторів запитів та лямбда-виразів).
Ендрю Заєць

Ця відповідь не має сенсу, оскільки коментарі BlueMonkMN.
Альф

0

Для виконання регістрових запитів Linq до Sql оголошуйте "рядкові" поля чутливими до регістру, вказуючи тип даних сервера, використовуючи одне з наступних;

varchar(4000) COLLATE SQL_Latin1_General_CP1_CS_AS 

або

nvarchar(Max) COLLATE SQL_Latin1_General_CP1_CS_AS

Примітка: "CS" у вищевказаних типах порівняння означає "Чутливий до регістру".

Це можна ввести у поле «Тип даних сервера» під час перегляду властивості за допомогою Visual Studio DBML Designer.

Докладніше див. Http://yourdotnetdesignteam.blogspot.com/2010/06/case-sensitive-linq-to-sql-queries.html


В цьому і полягає проблема. Зазвичай поле, яке я використовую, чутливе до регістру (хімічна формула CO [оксид вуглецю] відрізняється від Co [кобальту]). Однак у конкретній ситуації (пошук) я хочу, щоб co співпало як Co, так і CO. Визначення додаткової властивості з іншим "типом даних сервера" не є законним (linq to sql дозволяє лише одне властивість на sql стовпець). Так що все одно не їдь
doekman

Крім того, якщо робити тестування модулів, такий підхід, швидше за все, не можна порівняти з макетом даних. Найкраще використовувати підхід linq / lambda у прийнятій відповіді.
Деррік

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

1
Що таке текст SQL, в який це перекладається, і що дозволяє йому бути нечутливим до регістру в середовищі SQL, яке б інакше ставилося до нього як до регістру?
BlueMonkMN

0

Наступний двоступеневий підхід працює для мене (VS2010, ASP.NET MVC3, SQL Server 2008, Linq до 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
Цей код має помилку, якщо текст починається з пошукового тексту (повинен бути> = 0)
Flatliner DOA

@FlatlinerDOA насправді це має бути, != -1тому що IndexOf "повертається -1, якщо символ або рядок не знайдено"
drzaus

0

Іноді значення, що зберігаються в базі даних, можуть містити пробіли, тому запуск цього може бути помилковим

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

Вирішення цієї проблеми полягає в тому, щоб видалити простір, потім перетворити його корпус, а потім вибрати такий

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

Примітка в цьому випадку

customname - значення, яке відповідає значенню бази даних

UsersTB - це клас

назва стовпця База даних


-1

Пам'ятайте, що існує різниця між тим, чи працює запит, і чи працює він ефективно ! Оператор LINQ перетворюється на T-SQL, коли цілью оператора є SQL Server, тому потрібно думати про T-SQL, який буде створений.

Використання String.Equals швидше за все (я думаю) поверне всі рядки з SQL Server, а потім виконає порівняння в .NET, оскільки це .NET-вираз, який неможливо перекласти в T-SQL.

Іншими словами, використання виразу збільшить ваш доступ до даних та видалить вашу здатність використовувати індекси. Він буде працювати над невеликими таблицями, і ви не помітите різниці. На великому столі це може бути дуже погано.

Це одна з проблем, яка існує з LINQ; люди більше не думають про те, як будуть виконані заяви, які вони пишуть.

У цьому випадку немає способу зробити те, що ви хочете, не використовуючи вирази - навіть у T-SQL. Тому ви, можливо, не зможете зробити це більш ефективно. Навіть відповідь T-SQL, наведена вище (з використанням змінних із зіставленням), швидше за все, призведе до того, що індекси будуть ігноровані, але якщо це велика таблиця, тоді варто запустити оператор і подивитися план виконання, щоб побачити, чи використовувався індекс .


2
Це неправда (це не призводить до повернення рядків клієнту). Я використовував String.Equals, і причина, по якій він не працює, полягає в тому, що він перетворюється на порівняння рядків TSQL, поведінка якого залежить від зіставлення бази даних або сервера. Я, наприклад, враховую, як кожен вираз LINQ в SQL, який я записую, буде перетворений в TSQL. Шлях до того, що я хочу, - це використовувати ToUpper, щоб змусити згенерований TSQL використовувати UPPER. Тоді вся логіка перетворення та порівняння все ще виконується в TSQL, так що ви не втрачаєте великої продуктивності.
BlueMonkMN
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.