Сравнение с учетом регистра LINQ to Entities - PullRequest
109 голосов
/ 02 октября 2010

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

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

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

Ответы [ 8 ]

151 голосов
/ 02 октября 2010

Это потому, что вы используете 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 передается в Object Services API, который преобразует дерево выражений в дерево команд. Затем он отправляется поставщику магазина (например, SqlClient), который преобразует дерево команд в текст команды собственной базы данных. Запрос выполняется в хранилище данных, и результаты Материализованы в Объекты Entity от Объектные службы . Между ними не было заложено никакой логики, чтобы учесть чувствительность к регистру. Поэтому независимо от того, какой случай вы указали в своем предикате, он всегда будет обрабатываться как SQL Server, если вы не измените параметры сортировки SQL Server для этого столбца.

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

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

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

Для получения дополнительной информации о шаблонах SQL Server посмотрите SQL-запрос поиска SQL Server с учетом регистра

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

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

Thingies.Where(t => t.Name == "ThingamaBob")
        .AsEnumerable()
        .First(t => t.Name == "ThingamaBob");
10 голосов
/ 25 апреля 2015

Вы можете добавить аннотацию [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);
    }

Тогда сделай

Add-Migration CaseSensitive

Update-Database

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

9 голосов
/ 08 февраля 2015

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; }

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

1 голос
/ 10 ноября 2016

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

var firstCheck = Thingies.Where(t => t.Name == "ThingamaBob")
    .FirstOrDefault();
var doubleCheck = (firstCheck?.Name == model.Name) ? Thingies : null;
0 голосов
/ 04 февраля 2016

Использовать строку. Уравнения

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

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

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

Thingies.First(t => string.Equals(t.Name, "ThingamaBob", StringComparison.CurrentCultureIgnoreCase);
0 голосов
/ 30 апреля 2015

Ни один из StringComparison.IgnoreCase не работал для меня. Но это сделал:

context.MyEntities.Where(p => p.Email.ToUpper().Equals(muser.Email.ToUpper()));
0 голосов
/ 13 сентября 2013

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

Thingies
    .First(t => t.Name.Equals(
        "ThingamaBob",
        System.StringComparison.InvariantCultureIgnoreCase)
0 голосов
/ 26 сентября 2012

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

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

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

...