Porównanie LINQ to Entities z uwzględnieniem wielkości liter

115

To nie jest porównanie z rozróżnianiem wielkości liter w LINQ to Entities:

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

Jak mogę uzyskać porównanie z rozróżnianiem wielkości liter w LINQ to Entities?

Ronnie Overby
źródło
@Ronnie: jesteś tego pewien? Czy masz na myśli porównanie bez rozróżniania wielkości liter ?
Michael Petrotta
14
Absolutnie pewny. Nie, nie mam tego na myśli.
Ronnie Overby
12
Nie, na moim komputerze z systemem EF 4.0 w / SQL Server 2008 R2 wielkość liter nie ma znaczenia. Wiem, że wiele miejsc mówi, że EF domyślnie rozróżnia wielkość liter, ale nie tego doświadczyłem.
tster
3
Czy to nie będzie zależeć od podstawowej bazy danych?
codymanix
1
@codymanix: To dobre pytanie! Czy Linq na EF tłumaczy wyrażenie lambda dla zapytania DB? Nie znam odpowiedzi.
Tergiver

Odpowiedzi:

163

Dzieje się tak, ponieważ używasz LINQ To Entities, które ostatecznie konwertuje twoje wyrażenia Lambda na instrukcje SQL. Oznacza to, że rozróżnianie wielkości liter zależy od serwera SQL Server, który domyślnie ma sortowanie SQL_Latin1_General_CP1_CI_AS i NIE rozróżnia wielkości liter.

Użycie ObjectQuery.ToTraceString do zobaczenia wygenerowanego zapytania SQL, które zostało faktycznie przesłane do SQL Server, ujawnia tajemnicę:

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

Podczas tworzenia LINQ podmiotom kwerendy LINQ do podmiotów wykorzystuje parser LINQ, aby rozpocząć przetwarzanie zapytań i zamienia go na drzewie wyrażenie LINQ. Drzewo wyrażeń LINQ jest następnie przesyłane do interfejsu API usług obiektów , które konwertuje drzewo wyrażeń na drzewo poleceń. Następnie jest wysyłany do dostawcy sklepu (np. SqlClient), który konwertuje drzewo poleceń na tekst polecenia natywnej bazy danych. Zapytanie jest wykonywane w magazynie danych, a wyniki są materializowane w obiektach jednostek przez usługi obiektowe. Nie wprowadzono żadnej logiki, aby uwzględnić rozróżnianie wielkości liter. Więc bez względu na to, jaki przypadek umieścisz w swoim predykacie, będzie on zawsze traktowany jako taki sam przez serwer SQL Server, chyba że zmienisz sortowanie SQL Server dla tej kolumny.

Rozwiązanie po stronie serwera:

Dlatego najlepszym rozwiązaniem byłaby zmiana sortowania kolumny Name w tabeli Thingies na COLLATE Latin1_General_CS_AS, w której rozróżniana jest wielkość liter, uruchamiając to na serwerze SQL:

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

Aby uzyskać więcej informacji na temat SQL Server zestawia podjąć aa spojrzenie na SQL Server Sortowanie Case Sensitive SQL Query Szukaj

Rozwiązanie po stronie klienta:

Jedynym rozwiązaniem, które możesz zastosować po stronie klienta, jest użycie LINQ to Objects do wykonania kolejnego porównania, które nie wydaje się zbyt eleganckie:

Thingies.Where(t => t.Name == "ThingamaBob")
        .AsEnumerable()
        .First(t => t.Name == "ThingamaBob");
Morteza Manavi
źródło
Generuję schemat bazy danych za pomocą Entity Framework, więc rozwiązanie korzystające z mojego kodu wywołującego byłoby najlepsze. Myślę, że sprawdzę, kiedy wyniki wrócą. Dzięki.
Ronnie Overby
Nie ma problemu. Tak, to prawda i zaktualizowałem moją odpowiedź rozwiązaniem po stronie klienta, jednak nie jest to zbyt eleganckie i nadal zalecam korzystanie z rozwiązania do przechowywania danych.
Morteza Manavi
18
@eglasius To nie jest do końca prawda: nie pobiera WSZYSTKICH danych, pobiera tylko te dane, które nie uwzględniają wielkości liter, a następnie są ponownie filtrowane na kliencie z uwzględnieniem wielkości liter. Oczywiście, jeśli zdarzy się, że masz tysiące wpisów, które nie uwzględniają wielkości liter, ale tylko jeden z nich jest prawidłowy, z uwzględnieniem wielkości liter, oznacza to dużo narzutów. Ale nie sądzę, żeby rzeczywistość przedstawiała takie scenariusze ... :)
Achim
1
@MassoodKhaari To rozwiązanie, które opublikowałeś, sprawiłoby, że wielkość liter byłaby niewrażliwa, ponieważ zmniejszasz wielkość liter po obu stronach porównania. OP wymaga porównania z uwzględnieniem wielkości liter.
Jonny,
1
„Dlatego najlepszym rozwiązaniem byłaby zmiana sortowania kolumny Name w tabeli Thingies na COLLATE Latin1_General_CS_AS” - nie sądzę, żeby to było najlepsze. W większości przypadków potrzebuję filtra LIKE bez rozróżniania wielkości liter (.Contains ()), ale czasami powinien uwzględniać wielkość liter. Wypróbuję twoje „rozwiązanie po stronie klienta” - myślę, że jest o wiele bardziej eleganckie w moim przypadku użycia (byłoby miło zrozumieć, co robi, ale nie możesz mieć wszystkiego :)).
Niesamowity
11

Możesz dodać adnotację [CaseSensitive] dla EF6 + Code-first

Dodaj te zajęcia

[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());
    }
}

Zmodyfikuj swój DbContext, dodaj

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

Więc zrób

Add-Migration CaseSensitive

Zaktualizować bazę danych

na podstawie artykułu https://milinaudara.wordpress.com/2015/02/04/case-sensitive-search-using-entity-framework-with-custom-annotation/ z poprawką błędów

RouR
źródło
11

WHEREwarunki w SQL Server domyślnie nie uwzględniają wielkości liter. Uwzględnij wielkość liter, zmieniając domyślne sortowanie kolumny ( SQL_Latin1_General_CP1_CI_AS) na SQL_Latin1_General_CP1_CS_AS.

Kruchym sposobem na to jest użycie kodu. Dodaj nowy plik migracji, a następnie dodaj to wewnątrz Upmetody:

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

Ale

Możesz utworzyć niestandardową adnotację o nazwie „Uwzględnianie wielkości liter” za pomocą nowych funkcji EF6 i ozdobić swoje właściwości w następujący sposób:

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

Ten wpis na blogu wyjaśnia, jak to zrobić.

Milina Udara
źródło
W tym artykule mam błąd
RouR
3

Odpowiedź udzielona przez @Morteza Manavi rozwiązuje problem. Mimo to, dla rozwiązania po stronie klienta , elegancki sposób byłby następujący (dodanie podwójnego sprawdzenia).

var firstCheck = Thingies.Where(t => t.Name == "ThingamaBob")
    .FirstOrDefault();
var doubleCheck = (firstCheck?.Name == model.Name) ? Thingies : null;
Swarup Rajbhandari
źródło
-4

Podobała mi się odpowiedź Morteza i normalnie wolałbym to naprawić po stronie serwera. Po stronie klienta zwykle używam:

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

Zasadniczo najpierw sprawdź, czy jest użytkownik z wymaganymi kryteriami, a następnie sprawdź, czy hasło jest takie samo. Trochę rozwlekły, ale wydaje mi się, że jest łatwiejszy do odczytania, gdy w grę wchodzi cała masa kryteriów.

Rune Borgen
źródło
2
Ta odpowiedź oznacza, że ​​przechowujesz hasła jako zwykły tekst w swojej bazie danych, co stanowi ogromną lukę w zabezpieczeniach.
Jason Coyne
2
@JasonCoyne Hasło, z którym porównuje, może już zostać zaszyfrowane
Peter Morris
-4

Żaden z nich nie StringComparison.IgnoreCasedziałał dla mnie. Ale to zrobiło:

context.MyEntities.Where(p => p.Email.ToUpper().Equals(muser.Email.ToUpper()));
saquib adil
źródło
2
To nie pomogłoby w zadaniu pytania, które brzmi:How can I achieve case sensitive comparison
Reg Edit
-4

Użyj string.Equals

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

Nie musisz też martwić się o wartość null i odzyskać tylko potrzebne informacje.

Użyj StringComparision.CurrentCultureIgnoreCase dla bez uwzględniania wielkości liter.

Thingies.First(t => string.Equals(t.Name, "ThingamaBob", StringComparison.CurrentCultureIgnoreCase);
Darshan Joshi
źródło
Equals () nie może zostać przekonwertowane na SQL ... Również jeśli spróbujesz użyć metody instancji, StringComparison zostanie zignorowana.
LMK,
Czy wypróbowałeś to rozwiązanie? Próbowałem tego na końcu, dobrze współpracując z EF.
Darshan Joshi
-6

Nie jestem pewien co do EF4, ale EF5 obsługuje to:

Thingies
    .First(t => t.Name.Equals(
        "ThingamaBob",
        System.StringComparison.InvariantCultureIgnoreCase)
bloparod
źródło
Ciekawe, co generuje sql.
Ronnie Overby
Sprawdziłem to z EF5, po prostu wygenerował WHERE ... = ... w SQL. Więc znowu jest to zależne od ustawień sortowania po stronie serwera SQL.
Achim,
Nawet z rozróżnianiem wielkości liter w bazie danych nie mogłem uzyskać tego ani żadnego innego StringComparisonwyliczenia, aby coś zmienić. Widziałem wystarczająco dużo osób sugerujących, że tego rodzaju rzeczy powinny działać, aby pomyśleć, że problem jest gdzieś w pliku EDMX (db-first), chociaż stackoverflow.com/questions/841226/ ...
drzaus