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


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
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.