Порівняння рядків, що не враховують регістр, порівнюють у 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.