LINQ to Entities Groß- und Kleinschreibung vergleichen

115

Dies ist kein Vergleich zwischen Groß- und Kleinschreibung in LINQ und Entitäten:

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

Wie kann ich einen Vergleich zwischen Groß- und Kleinschreibung mit LINQ to Entities erzielen?

Ronnie Overby
quelle
@ Ronnie: Bist du dir da sicher? Meinen Sie einen Vergleich ohne Berücksichtigung der Groß- und Kleinschreibung ?
Michael Petrotta
14
Absolut sicher. Nein, das meine ich nicht.
Ronnie Overby
12
Nein, auf meinem Computer mit EF 4.0 mit SQL Server 2008 R2 wird die Groß- und Kleinschreibung nicht berücksichtigt. Ich weiß, dass viele Orte sagen, dass EF standardmäßig zwischen Groß- und Kleinschreibung unterscheidet, aber das habe ich nicht erlebt.
tster
3
Wird das nicht von der zugrunde liegenden Datenbank abhängen?
Codymanix
1
@codymanix: Das ist eine gute Frage! Übersetzt Linq to EF den Lambda-Ausdruck für eine DB-Abfrage? Ich weiß die Antwort nicht.
Tergiver

Antworten:

163

Dies liegt daran, dass Sie LINQ To Entities verwenden, mit dem Ihre Lambda-Ausdrücke letztendlich in SQL-Anweisungen konvertiert werden. Dies bedeutet, dass die Groß- und Kleinschreibung Ihrem SQL Server ausgeliefert ist, der standardmäßig über die SQL_Latin1_General_CP1_CI_AS-Kollatierung verfügt und bei der NICHT zwischen Groß- und Kleinschreibung unterschieden wird.

Die Verwendung von ObjectQuery.ToTraceString zum Anzeigen der generierten SQL-Abfrage, die tatsächlich an SQL Server gesendet wurde , enthüllt das Rätsel:

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

Wenn Sie eine LINQ to Entities- Abfrage erstellen , nutzt LINQ to Entities den LINQ-Parser, um mit der Verarbeitung der Abfrage zu beginnen und sie in einen LINQ-Ausdrucksbaum zu konvertieren. Der LINQ-Ausdrucksbaum wird dann an die Object Services- API übergeben, die den Ausdrucksbaum in einen Befehlsbaum konvertiert. Es wird dann an den Speicheranbieter (z. B. SqlClient) gesendet, der den Befehlsbaum in den nativen Datenbankbefehlstext konvertiert. Die Abfrage wird im Datenspeicher ausgeführt und die Ergebnisse werden von Object Services in Entitätsobjekte materialisiert. Dazwischen wurde keine Logik eingefügt, um die Groß- und Kleinschreibung zu berücksichtigen. Unabhängig davon, in welchem ​​Fall Sie Ihr Prädikat eingeben, wird es von Ihrem SQL Server immer als gleich behandelt, es sei denn, Sie ändern Ihre SQL Server-Sortierungen für diese Spalte.

Serverseitige Lösung:

Daher besteht die beste Lösung darin, die Sortierung der Spalte Name in der Thingies- Tabelle in COLLATE Latin1_General_CS_AS zu ändern. Dabei wird zwischen Groß- und Kleinschreibung unterschieden, indem Sie dies auf Ihrem SQL Server ausführen :

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

Weitere Informationen zu den SQL Server-Sortierungen finden Sie unter SQL Server-Sortierung Groß- und Kleinschreibung beachten

Client-seitige Lösung:

Die einzige Lösung, die Sie auf der Clientseite anwenden können, besteht darin, LINQ to Objects zu verwenden, um einen weiteren Vergleich durchzuführen, der nicht sehr elegant zu sein scheint:

Thingies.Where(t => t.Name == "ThingamaBob")
        .AsEnumerable()
        .First(t => t.Name == "ThingamaBob");
Morteza Manavi
quelle
Ich generiere das Datenbankschema mit Entity Framework, daher ist eine Lösung mit meinem Aufrufcode am besten. Ich denke, ich werde eine Überprüfung durchführen, nachdem die Ergebnisse zurückgekommen sind. Vielen Dank.
Ronnie Overby
Kein Problem. Ja, das ist richtig und ich habe meine Antwort mit einer clientseitigen Lösung aktualisiert. Sie ist jedoch nicht sehr elegant und ich empfehle weiterhin, die Datenspeicherlösung zu verwenden.
Morteza Manavi
18
@eglasius Dies ist nicht ganz richtig: Es werden nicht ALLE Daten abgerufen, es werden nur die Daten abgerufen, die ohne Berücksichtigung der Groß- und Kleinschreibung übereinstimmen, und danach wird der Groß- und Kleinschreibung des Clients erneut sensibel gefiltert. Wenn Sie zufällig Tausende von Einträgen haben, bei denen die Groß- und Kleinschreibung nicht berücksichtigt wird, aber nur einer von ihnen die richtige Groß- und Kleinschreibung berücksichtigt, ist der Aufwand sehr hoch. Aber ich glaube nicht, dass die Realität solche Szenarien präsentieren wird ... :)
Achim
1
@MassoodKhaari Diese von Ihnen veröffentlichte Lösung würde die Groß- und Kleinschreibung nicht berücksichtigen, da Sie beide Seiten des Vergleichs niedriger halten. Das OP benötigt einen Vergleich zwischen Groß- und Kleinschreibung.
Jonny
1
"Daher wäre die beste Lösung, die Sortierung der Spalte Name in der Thingies-Tabelle in COLLATE Latin1_General_CS_AS zu ändern." Ich denke nicht, dass dies die beste ist. Die meiste Zeit benötige ich einen LIKE-Filter ohne Groß- und Kleinschreibung (.Contains ()), aber manchmal sollte zwischen Groß- und Kleinschreibung unterschieden werden. Ich werde Ihre "clientseitige Lösung" ausprobieren - sie ist meiner Meinung nach viel eleganter für meinen Anwendungsfall (wäre schön zu verstehen, was sie tut, aber Sie können nicht alles haben :)).
Der unglaubliche
11

Sie können eine [CaseSensitive] -Anmerkung für EF6 + Code-first hinzufügen

Fügen Sie diese Klassen hinzu

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

Ändern Sie Ihren DbContext, fügen Sie hinzu

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

Dann mach

Add-Migration CaseSensitive

Datenbank auf den neusten Stand bringen

basierend auf Artikel https://milinaudara.wordpress.com/2015/02/04/case-sensitive-search-using-entity-framework-with-custom-annotation/ mit einigen Fehlerkorrekturen

RouR
quelle
11

WHEREBei Bedingungen in SQL Server wird standardmäßig die Groß- und Kleinschreibung nicht berücksichtigt. Ändern Sie die Groß- und Kleinschreibung, indem Sie die Standardkollatierungen ( SQL_Latin1_General_CP1_CI_AS) der Spalte in ändern SQL_Latin1_General_CP1_CS_AS.

Der fragile Weg, dies zu tun, ist mit Code. Fügen Sie eine neue Migrationsdatei hinzu und fügen Sie diese dann in die UpMethode ein:

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

Aber

Mit den neuen EF6-Funktionen können Sie benutzerdefinierte Anmerkungen mit dem Namen "CaseSensitive" erstellen und Ihre Eigenschaften wie folgt dekorieren:

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

In diesem Blogbeitrag wird erklärt, wie das geht.

Milina Udara
quelle
In diesem Artikel haben einen Fehler
RouR
3

Die Antwort von @Morteza Manavi löst das Problem. Für eine clientseitige Lösung wäre ein eleganter Weg jedoch der folgende (Hinzufügen einer doppelten Prüfung).

var firstCheck = Thingies.Where(t => t.Name == "ThingamaBob")
    .FirstOrDefault();
var doubleCheck = (firstCheck?.Name == model.Name) ? Thingies : null;
Swarup Rajbhandari
quelle
-4

Ich mochte Mortezas Antwort und würde es normalerweise vorziehen, sie auf der Serverseite zu reparieren. Für die Client-Seite verwende ich normalerweise:

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

Überprüfen Sie zunächst, ob es einen Benutzer mit den erforderlichen Kriterien gibt, und prüfen Sie dann, ob das Kennwort identisch ist. Ein bisschen langatmig, aber ich denke, es ist einfacher zu lesen, wenn es eine ganze Reihe von Kriterien gibt.

Rune Borgen
quelle
2
Diese Antwort impliziert, dass Sie Kennwörter als einfachen Text in Ihrer Datenbank speichern, was eine große Sicherheitslücke darstellt.
Jason Coyne
2
@ JasonCoyne Das Passwort, mit dem er vergleicht, könnte bereits gehasht werden
Peter Morris
-4

Keiner von beiden StringComparison.IgnoreCasehat für mich funktioniert. Aber das tat:

context.MyEntities.Where(p => p.Email.ToUpper().Equals(muser.Email.ToUpper()));
Saquib Adil
quelle
2
Dies würde nicht helfen bei der Frage, die gestellt wurde, nämlichHow can I achieve case sensitive comparison
Reg Edit
-4

Verwenden Sie string.Equals

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

Außerdem müssen Sie sich keine Sorgen um Null machen und nur die gewünschten Informationen zurückerhalten.

Verwenden Sie StringComparision.CurrentCultureIgnoreCase für Groß- und Kleinschreibung.

Thingies.First(t => string.Equals(t.Name, "ThingamaBob", StringComparison.CurrentCultureIgnoreCase);
Darshan Joshi
quelle
Equals () kann nicht in SQL konvertiert werden ... Auch wenn Sie versuchen, die Instanzmethode zu verwenden, wird der StringComparison ignoriert.
LMK
Haben Sie diese Lösung ausprobiert? Ich habe es am Ende versucht, um gut mit EF zusammenzuarbeiten.
Darshan Joshi
-6

Ich bin mir bei EF4 nicht sicher, aber EF5 unterstützt dies:

Thingies
    .First(t => t.Name.Equals(
        "ThingamaBob",
        System.StringComparison.InvariantCultureIgnoreCase)
Bloparod
quelle
Neugierig, welche SQL das generiert.
Ronnie Overby
Ich habe dies mit EF5 überprüft, es wurde einfach ein WHERE ... = ... in SQL generiert. Dies hängt also wiederum von den Sortiereinstellungen auf der SQL Server-Seite ab.
Achim
Selbst mit einer Sortierung, bei der zwischen Groß- und Kleinschreibung unterschieden wird, konnte ich diese oder eine der anderen StringComparisonAufzählungen nicht dazu bringen, einen Unterschied zu machen. Ich habe genug Leute gesehen, die vorgeschlagen haben, dass so etwas funktionieren sollte, um zu glauben, dass das Problem irgendwo in der EDMX-Datei (db-first) liegt, obwohl stackoverflow.com/questions/841226/…
drzaus