Сравнение строк без учета регистра в LINQ-to-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» в базе данных с учетом регистра.

BlueMonkMN
источник
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

Ответы:

111

Как вы говорите, между ToUpper и ToLower есть несколько важных различий, и только одно из них является надежно точным, когда вы пытаетесь выполнять проверки равенства без учета регистра.

В идеале, лучший способ сделать регистронезависимую проверку равенства будет :

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

ПРИМЕЧАНИЕ, ОДНАКО это не работает в данном случае! Поэтому мы застряли на ToUpperили ToLower.

Обратите внимание на Ordinal IgnoreCase, чтобы сделать его безопасным. Но именно тот тип проверки с учетом регистра, который вы используете, зависит от ваших целей. Но обычно используйте Equals для проверки равенства и Compare при сортировке, а затем выбирайте правильный StringComparison для задания.

Майкл Каплан (признанный авторитет в области культуры и обращения с персонажами, подобного этому) написал соответствующие посты о ToUpper vs. ToLower:

Он говорит: «String.ToUpper - используйте ToUpper, а не ToLower, и укажите InvariantCulture, чтобы подобрать правила корпуса ОС ».

Эндрю Арнотт
источник
1
Кажется, это не относится к SQL Server: print upper ('Große Straße') возвращает GROßE STRAßE
BlueMonkMN
1
Кроме того, предоставленный вами пример кода имеет ту же проблему, что и предоставленный мной код, поскольку он чувствителен к регистру при запуске через LINQ-to-SQL в базе данных MS SQL 2005.
BlueMonkMN
2
Согласен. Извините, я не понял. Приведенный мной пример кода не работает с Linq2Sql, как вы указали в своем исходном вопросе. Я просто повторил, что путь, которым вы начали, был отличным - если бы он только работал в этом сценарии. И да, еще одна мыльница Майка Каплана заключается в том, что обработка символов SQL Server повсюду. Если вам нужна нечувствительность к регистру и вы не можете получить его другим способом, я предлагал (непонятно) хранить данные в верхнем регистре, а затем запрашивать их в верхнем регистре.
Эндрю Арнотт
3
Что ж, если у вас есть база данных, чувствительная к регистру, и вы храните в смешанном регистре и выполняете поиск в верхнем регистре, вы не получите совпадений. Если вы добавляете и данные, и запрос в свой поиск, то вы конвертируете весь текст, который вы ищете, для каждого запроса, который не является эффективным.
Эндрю Арнотт
1
@BlueMonkMN, вы уверены, что вставили правильные фрагменты? Трудно поверить, что MSSQL Server предпочитает красный цвет черному.
greenoldman
74

Я использовал System.Data.Linq.SqlClient.SqlMethods.Like(row.Name, "test") в своем запросе.

Это выполняет сравнение без учета регистра.

Эндрю Дэви
источник
3
ха! уже несколько лет использую linq 2 sql, но до сих пор не видел SqlMethods, спасибо!
Карл Хёрберг,
3
Гениально! Хотя можно было бы использовать более подробную информацию. Это одно из ожидаемых применений Like? Существуют ли возможные входные данные, которые могут вызвать ложноположительный результат? Или ложноотрицательный результат? В документации по этому методу отсутствует, где документация , которая будет описана работа метода Как?
Задача
2
Я думаю, это просто зависит от того, как SQL Server сравнивает строки, которые, вероятно, можно где-то настроить.
Эндрю Дэйви
12
System.Data.Linq.SqlClient.SqlMethods.Like (row.Name, «test») совпадает с row.Name.Contains («test»). Как говорит Эндрю, это зависит от параметров сортировки sql server. Таким образом, Like (or contains) не всегда выполняет сравнение без учета регистра.
doekman
3
Имейте в виду, что это делает код слишком похожим на SqlClient.
Jaider
4

Я пробовал это с помощью лямбда-выражения, и это сработало.

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

Виннахр
источник
19
Это потому, что вы используете a List<>, что означает, что сравнение происходит в памяти (код C #), а не IQueryable(или ObjectQuery), который будет выполнять сравнение в базе данных .
drzaus
1
Что сказал @drzaus. Этот ответ просто неверен, учитывая, что контекст - это linq2sql, а не обычный linq.
rsenna
0

Если вы передадите строку без учета регистра в LINQ-to-SQL, она будет передана в SQL без изменений, и сравнение будет происходить в базе данных. Если вы хотите выполнять сравнение строк в базе данных без учета регистра, все, что вам нужно сделать, это создать лямбда-выражение, которое выполняет сравнение, и поставщик LINQ-to-SQL переведет это выражение в SQL-запрос с сохранением вашей строки.

Например, этот запрос LINQ:

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

преобразуется поставщиком LINQ-to-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
Я хочу выполнить сравнение без учета регистра в базе данных с учетом регистра.
BlueMonkMN
Какую базу данных CASE-SENSITIVE вы используете?
Эндрю Хэйр
Кроме того, запрос LINQ не является лямбда-выражением. Запрос LINQ состоит из нескольких частей (в первую очередь операторов запроса и лямбда-выражений).
Эндрю Хэйр
Этот ответ не имеет смысла, как комментирует BlueMonkMN.
Alf
0

Для выполнения запросов Linq to 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 в принятом ответе.
Derrick
0
where row.name.StartsWith(q, true, System.Globalization.CultureInfo.CurrentCulture)
Хулио Сильвейра
источник
1
В какой текст SQL это переводится и что позволяет ему быть нечувствительным к регистру в среде SQL, которая иначе трактовала бы его как чувствительный к регистру?
BlueMonkMN
0

Для меня работает следующий двухэтапный подход (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
В этом коде есть ошибка, если текст начинается с поискового текста (должно быть> = 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 - это значение, которое соответствует значению базы данных

UsersTBs - это класс

заголовок - столбец базы данных

ТАХА СУЛТАН ТЕМУРИ
источник
-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 to SQL будет преобразовано в TSQL. Я хочу использовать ToUpper, чтобы заставить сгенерированный TSQL использовать UPPER. Тогда вся логика преобразования и сравнения по-прежнему выполняется в TSQL, поэтому вы не сильно теряете производительность.
BlueMonkMN 07