Сравнение LINQ to Entities с учетом регистра

115

Это сравнение в LINQ to Entities не учитывает регистр:

Thingies.First(t => t.Name == "ThingamaBob");

Как добиться сравнения с учетом регистра с LINQ to Entities?

Ронни Оверби
источник
@ Ронни: ты в этом уверен? Вы имеете в виду сравнение без учета регистра ?
Майкл Петротта, 01
14
Абсолютно уверен. Нет, я не это имел в виду.
Ронни Оверби,
12
Нет, на моем компьютере с EF 4.0 с SQL Server 2008 R2 выше регистр не учитывается. Я знаю, что во многих местах говорится, что EF чувствителен к регистру по умолчанию, но это не то, что я испытал.
tster
3
Разве это не будет зависеть от базовой базы данных?
codymanix
1
@codymanix: Хороший вопрос! Преобразует ли Linq в EF лямбда-выражение для запроса БД? Я не знаю ответа.
Tergiver,

Ответы:

163

Это потому, что вы используете LINQ To Entities, который в конечном итоге преобразует ваши лямбда-выражения в операторы SQL. Это означает, что чувствительность к регистру находится во власти вашего SQL Server, который по умолчанию имеет сопоставление SQL_Latin1_General_CP1_CI_AS и НЕ чувствителен к регистру.

Использование ObjectQuery.ToTraceString для просмотра сгенерированного запроса SQL, который был фактически отправлен в SQL Server, раскрывает тайну:

string sqlQuery = ((ObjectQuery)context.Thingies
        .Where(t => t.Name == "ThingamaBob")).ToTraceString();

Когда вы создаете запрос LINQ to Entities , LINQ to Entities использует синтаксический анализатор LINQ, чтобы начать обработку запроса и преобразовать его в дерево выражений LINQ. Затем дерево выражений LINQ передается в API объектных служб , который преобразует дерево выражений в дерево команд. Затем он отправляется поставщику хранилища (например, SqlClient), который преобразует дерево команд в текст команды собственной базы данных. Запрос выполняется в хранилище данных, и результаты материализуются в объекты сущности с помощью служб объектов., Никакой логики для учета чувствительности к регистру не использовалось. Таким образом, независимо от того, какой регистр вы укажете в своем предикате, он всегда будет обрабатываться вашим SQL Server как один, если вы не измените параметры сортировки SQL Server для этого столбца.

Решение на стороне сервера:

Поэтому лучшим решением было бы изменить параметры сортировки столбца Name в таблице Thingies на COLLATE Latin1_General_CS_AS, который чувствителен к регистру, запустив это на вашем SQL Server:

ALTER TABLE Thingies
ALTER COLUMN Name VARCHAR(25)
COLLATE Latin1_General_CS_AS

Для получения дополнительной информации о SQL Server Collates , взгляните на SQL SERVER Collate с учетом регистра SQL Query Search

Клиентское решение:

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

Thingies.Where(t => t.Name == "ThingamaBob")
        .AsEnumerable()
        .First(t => t.Name == "ThingamaBob");
Мортеза Манави
источник
Я создаю схему базы данных с помощью Entity Framework, поэтому лучше всего будет использовать мой вызывающий код. Думаю, я сделаю проверку после того, как вернутся результаты. Спасибо.
Ронни Оверби,
Нет проблем. Да, это правильно, и я обновил свой ответ с помощью решения на стороне клиента, однако оно не очень элегантно, и я все же рекомендую использовать решение для хранилища данных.
Мортеза Манави,
18
@eglasius Это не совсем так: он не извлекает ВСЕ данные, он извлекает только те данные, которые соответствуют регистру без учета регистра, а после этого снова фильтруются с учетом регистра клиента. Конечно, если у вас есть тысячи записей, которые нечувствительны к регистру, но только одна из них является правильной, одна чувствительна к регистру, тогда это накладные расходы. Но я не думаю, что реальность представит такие сценарии ... :)
Ахим
1
@MassoodKhaari Это решение, которое вы опубликовали, сделает его нечувствительным к регистру, потому что вы уменьшаете обе стороны сравнения. OP требует сравнения с учетом регистра.
Джонни
1
«Следовательно, лучшим решением было бы изменить параметры сортировки столбца Name в таблице Thingies на COLLATE Latin1_General_CS_AS» - я не думаю, что это лучший вариант. В большинстве случаев мне нужен фильтр LIKE без учета регистра (.Contains ()), но иногда он должен быть чувствительным к регистру. Я попробую ваше "клиентское решение" - я думаю, оно намного элегантнее для моего случая использования (было бы неплохо понять, что оно делает, но у вас не может быть всего этого :)).
Невероятный
11

Вы можете добавить аннотацию [CaseSensitive] для EF6 + Code-first

Добавить эти классы

[AttributeUsage(AttributeTargets.Property, AllowMultiple = true)]
public class CaseSensitiveAttribute : Attribute
{
    public CaseSensitiveAttribute()
    {
        IsEnabled = true;
    }
    public bool IsEnabled { get; set; }
}

public class CustomSqlServerMigrationSqlGenerator : SqlServerMigrationSqlGenerator
{
    protected override void Generate(AlterColumnOperation alterColumnOperation)
    {
        base.Generate(alterColumnOperation);
        AnnotationValues values;
        if (alterColumnOperation.Column.Annotations.TryGetValue("CaseSensitive", out values))
        {
            if (values.NewValue != null && values.NewValue.ToString() == "True")
            {
                using (var writer = Writer())
                {
                    //if (System.Diagnostics.Debugger.IsAttached == false) System.Diagnostics.Debugger.Launch();

                    // https://github.com/mono/entityframework/blob/master/src/EntityFramework.SqlServer/SqlServerMigrationSqlGenerator.cs
                    var columnSQL = BuildColumnType(alterColumnOperation.Column); //[nvarchar](100)
                    writer.WriteLine(
                        "ALTER TABLE {0} ALTER COLUMN {1} {2} COLLATE SQL_Latin1_General_CP1_CS_AS {3}",
                        alterColumnOperation.Table,
                        alterColumnOperation.Column.Name,
                        columnSQL,
                        alterColumnOperation.Column.IsNullable.HasValue == false || alterColumnOperation.Column.IsNullable.Value == true ? " NULL" : "NOT NULL" //todo not tested for DefaultValue
                        );
                    Statement(writer);
                }
            }
        }
    }
}

public class CustomApplicationDbConfiguration : DbConfiguration
{
    public CustomApplicationDbConfiguration()
    {
        SetMigrationSqlGenerator(
            SqlProviderServices.ProviderInvariantName,
            () => new CustomSqlServerMigrationSqlGenerator());
    }
}

Измените свой DbContext, добавьте

protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        modelBuilder.Conventions.Add(new AttributeToColumnAnnotationConvention<CaseSensitiveAttribute, bool>(
                "CaseSensitive",
                (property, attributes) => attributes.Single().IsEnabled));
        base.OnModelCreating(modelBuilder);
    }

Тогда сделай

Надстройка миграции CaseSensitive

Обновление базы данных

на основе статьи https://milinaudara.wordpress.com/2015/02/04/case-sensitive-search-using-entity-framework-with-custom-annotation/ с исправлением некоторых ошибок

RouR
источник
11

WHEREУсловия в SQL Server по умолчанию нечувствительны к регистру. Сделайте регистр чувствительным, изменив параметры сортировки столбца по умолчанию ( SQL_Latin1_General_CP1_CI_AS) на SQL_Latin1_General_CP1_CS_AS.

Хрупкий способ сделать это - использовать код. Добавьте новый файл миграции, а затем добавьте его внутри Upметода:

public override void Up()
{
   Sql("ALTER TABLE Thingies ALTER COLUMN Name VARCHAR(MAX) COLLATE SQL_Latin1_General_CP1_CS_AS NOT NULL");
}

Но

Вы можете создать настраиваемую аннотацию под названием CaseSensitive, используя новые функции EF6, и украсить свои свойства следующим образом:

[CaseSensitive]
public string Name { get; set; }

В этом сообщении блога объясняется, как это сделать.

Милина Удара
источник
В этой статье есть ошибка
RouR
3

Ответ @Morteza Manavi решает проблему. Тем не менее, для решения на стороне клиента элегантным способом будет следующий (добавление двойной проверки).

var firstCheck = Thingies.Where(t => t.Name == "ThingamaBob")
    .FirstOrDefault();
var doubleCheck = (firstCheck?.Name == model.Name) ? Thingies : null;
Сваруп Раджбхандари
источник
-4

Мне понравился ответ Мортеза, и обычно я предпочел бы исправить на стороне сервера. На стороне клиента я обычно использую:

Dim bLogin As Boolean = False

    Dim oUser As User = (From c In db.Users Where c.Username = UserName AndAlso c.Password = Password Select c).SingleOrDefault()
    If oUser IsNot Nothing Then
        If oUser.Password = Password Then
            bLogin = True
        End If
    End If

По сути, сначала проверяется, есть ли пользователь с требуемыми критериями, а затем проверяется, тот же ли пароль. Немного многословно, но я чувствую, что его легче читать, когда может быть задействован целый ряд критериев.

Руна Борген
источник
2
Этот ответ подразумевает, что вы храните пароли в виде простого текста в своей базе данных, что является огромной уязвимостью безопасности.
Джейсон Койн,
2
@JasonCoyne Пароль, с которым он сравнивает, уже мог быть хеширован
Питер Моррис,
-4

Ни один из них не StringComparison.IgnoreCaseработал у меня. Но это произошло:

context.MyEntities.Where(p => p.Email.ToUpper().Equals(muser.Email.ToUpper()));
сакиб адил
источник
2
Это позволило бы не помочь с вопросом , который был задан вопрос, который,How can I achieve case sensitive comparison
Reg Edit
-4

Используйте string.Equals

Thingies.First(t => string.Equals(t.Name, "ThingamaBob", StringComparison.CurrentCulture);

Кроме того, вам не нужно беспокоиться о null и получить только ту информацию, которую вы хотите.

Используйте StringComparision.CurrentCultureIgnoreCase для нечувствительности к регистру.

Thingies.First(t => string.Equals(t.Name, "ThingamaBob", StringComparison.CurrentCultureIgnoreCase);
Даршан Джоши
источник
Equals () не может быть преобразован в SQL ... Также, если вы попытаетесь использовать метод экземпляра, StringComparison игнорируется.
LMK
Вы пробовали это решение? Я попробовал это со своей стороны, чтобы нормально работать с EF.
Даршан Джоши
-6

Не уверен насчет EF4, но EF5 это поддерживает:

Thingies
    .First(t => t.Name.Equals(
        "ThingamaBob",
        System.StringComparison.InvariantCultureIgnoreCase)
bloparod
источник
Любопытно, что генерирует sql.
Ронни Оверби
Я проверил это с помощью EF5, он просто сгенерировал WHERE ... = ... в SQL. Опять же, это зависит от параметров сортировки на стороне SQL-сервера.
Achim
Даже с учетом регистра в базе данных я не мог заставить это или любое другое StringComparisonперечисление иметь значение. Я видел достаточно людей, предлагающих такие вещи, чтобы думать, что проблема где-то в файле EDMX (db-first), хотя stackoverflow.com/questions/841226/…
drzaus