Порівняння з великим регістром від LINQ до Entities


115

Це не порівнянне з великим регістром порівняння LINQ до об'єктів:

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

Як я можу домогтися порівняння з великим регістром порівняння з LINQ до організацій?


@ Роні: ти впевнений у цьому? Ви маєте на увазі порівняльне нечутливе порівняння?
Майкл Петротта

14
Абсолютно впевнений. Ні, я цього не маю на увазі.
Ронні Овербі

12
Ні, на моєму комп’ютері, що працює під керуванням EF 4.0 w / SQL Server 2008 R2, вищезазначене є нечутливим до регістру. Я знаю, що багато місць говорять про те, що EF є великим регістром, але це не те, що я відчував.
tster

3
Чи це не залежатиме від основної бази даних?
codymanix

1
@codymanix: Це гарне запитання! Чи перекладає Linq на EF вираз лямбда для запиту БД? Я не знаю відповіді.
Тергівер

Відповіді:


163

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

Використовуючи ObjectQuery.ToTraceString, щоб побачити згенерований SQL-запит, який був фактично поданий на SQL Server, розкриває таємницю:

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

Коли ви створюєте запит LINQ до Entities , LINQ to Entities використовує парсер LINQ для початку обробки запиту і перетворює його в дерево вираження LINQ. Потім дерево виразів LINQ передається в API об’єктних служб , який перетворює дерево виразів у дерево команд. Потім він надсилається постачальнику магазину (наприклад, SqlClient), який перетворює дерево команд у рідний текст команди бази даних. Query отримати виконується в сховище даних , і результати Матеріалізовані в Entity об'єктів по обслуговуванню об'єктів. Жодна логіка між ними не враховувала чутливість до справ. Таким чином, незалежно від того, у якому випадку ви ставите свій предикат, він завжди вважатиметься вашим SQL сервером таким самим, якщо ви не зміните SQL Server Collati для цього стовпця.

Рішення на стороні сервера:

Тому найкращим рішенням було б змінити зіставлення стовпця " Ім'я " в таблиці Thingies на COLLATE Latin1_General_CS_AS, який залежно від регістру, запустивши це на вашому SQL Server:

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

Щоб отримати додаткові відомості про SQL Server Collati , подивіться на пошук SQL SERVER Collate Sensitive SQL Query Search

Рішення на стороні клієнта:

Єдине рішення, яке можна застосувати на стороні клієнта, - це використовувати LINQ для об’єктів, щоб зробити ще одне порівняння, яке не дуже елегантно:

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

Я генерую схему бази даних за допомогою Entity Framework, тому рішення, яке використовує мій код виклику, було б найкращим. Напевно, я зроблю перевірку після того, як результати повернуться. Дякую.
Ронні Овербі

Нема проблем. Так, це правильно, і я оновив свою відповідь рішенням на стороні клієнта, однак це не дуже елегантно, і я все ж рекомендую використовувати рішення для зберігання даних.
Morteza Manavi

18
@eglasius Це не зовсім вірно: він не отримує ВСІ дані, він отримує лише ті дані, які невідчутливі відповідно до регістру, і після цього вони знову фільтруються на клієнтській справі. Звичайно, якщо у вас є тисячі записів, які відповідають невідчутним регістром, але лише одна з них є правильною, залежно від регістру, то це дуже багато витрат. Але я не думаю, що реальність буде представляти такі сценарії ... :)
Ахім

1
@MassoodKhaari Це рішення, яке ви опублікували, зробить його Case Insensitive, оскільки ви нижній корпус порівняння. ОП потребує порівняння з урахуванням регістру.
Джоні

1
"Тому найкращим рішенням було б змінити зіставлення стовпця" Ім'я "в таблиці 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);
    }

Тоді робіть

Справа щодо міграції, сприйнятлива

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

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


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
Редагувати

-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 ... Також якщо ви спробуєте використати метод екземпляра, StringComppare ігнорується.
LMK

Ви спробували це рішення? Я спробував це в кінці, коли добре працював з EF.
Даршан Джоші

-6

Не впевнений у EF4, але EF5 підтримує це:

Thingies
    .First(t => t.Name.Equals(
        "ThingamaBob",
        System.StringComparison.InvariantCultureIgnoreCase)

Цікаво, що sql, що генерує.
Ронні Овербі

Я перевірив це за допомогою EF5, він просто генерував WHERE ... = ... у SQL. Отже, це знову-таки залежить від параметрів зіставлення на стороні SQL-сервера.
Ахім

Навіть при порівнянні з регістром порівняння в БД я не зміг отримати цей або будь-який інший StringComparisonперелік, щоб змінити ситуацію. Я бачив досить багато людей , які передбачають такого роду речі повинні працювати , щоб думати , що проблема де - то в файлі EDMX (DB-перше), хоча stackoverflow.com/questions/841226 / ...
drzaus
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.