Confronto con distinzione tra maiuscole e minuscole LINQ to Entities


115

Questo non è un confronto con distinzione tra maiuscole e minuscole in LINQ to Entities:

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

Come posso ottenere un confronto con distinzione tra maiuscole e minuscole con LINQ to Entities?


@Ronnie: ne sei sicuro? Intendi il confronto senza distinzione tra maiuscole e minuscole ?
Michael Petrotta

14
Assolutamente sicuro. No, non intendo questo.
Ronnie Overby

12
No, sul mio computer che esegue EF 4.0 con SQL Server 2008 R2, quanto sopra non fa distinzione tra maiuscole e minuscole. Conosco molti posti che dicono che EF fa distinzione tra maiuscole e minuscole per impostazione predefinita, ma non è quello che ho sperimentato.
tster

3
Non dipenderà dal database sottostante?
codymanix

1
@codymanix: Questa è una bella domanda! Linq in EF traduce l'espressione lambda per una query DB? Non conosco la risposta
Tergiver

Risposte:


163

Questo perché stai usando LINQ To Entities che alla fine converte le tue espressioni Lambda in istruzioni SQL. Ciò significa che la distinzione tra maiuscole e minuscole è alla mercé del tuo SQL Server che per impostazione predefinita ha SQL_Latin1_General_CP1_CI_AS Collation e NON distingue tra maiuscole e minuscole.

L'utilizzo di ObjectQuery.ToTraceString per visualizzare la query SQL generata che è stata effettivamente inviata a SQL Server rivela il mistero:

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

Quando si crea una query LINQ to Entities , LINQ to Entities sfrutta il parser LINQ per iniziare a elaborare la query e la converte in un albero delle espressioni LINQ. L'albero delle espressioni LINQ viene quindi passato all'API Object Services , che converte l'albero delle espressioni in un albero dei comandi. Viene quindi inviato al provider del negozio (ad esempio SqlClient), che converte l'albero dei comandi nel testo del comando del database nativo. La query viene eseguita sull'archivio dati ei risultati vengono materializzati in oggetti entità da Object Services. Non è stata inserita alcuna logica per tenere conto della distinzione tra maiuscole e minuscole. Quindi, indipendentemente dal caso in cui si inserisce il predicato, verrà sempre considerato lo stesso dal proprio SQL Server a meno che non si modifichino i fascicoli di SQL Server per quella colonna.

Soluzione lato server:

Pertanto, la soluzione migliore sarebbe modificare le regole di confronto della colonna Nome nella tabella Thingies in COLLATE Latin1_General_CS_AS, che distingue tra maiuscole e minuscole eseguendolo su SQL Server:

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

Per ulteriori informazioni sulle fascicelle di SQL Server , consultare Ricerca query SQL con distinzione tra maiuscole e minuscole in SQL SERVER

Soluzione lato client:

L'unica soluzione che puoi applicare sul lato client è usare LINQ to Objects per fare ancora un altro confronto che non sembra essere molto elegante:

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

Sto generando lo schema del database con Entity Framework, quindi una soluzione che utilizzi il mio codice chiamante sarebbe la migliore. Immagino che farò un controllo dopo che i risultati saranno tornati. Grazie.
Ronnie Overby

Nessun problema. Sì, è corretto e ho aggiornato la mia risposta con una soluzione lato client, tuttavia non è molto elegante e consiglio comunque di utilizzare la soluzione dell'archivio dati.
Morteza Manavi

18
@eglasius Questo non è completamente vero: non recupera TUTTI i dati, recupera solo i dati che corrispondono alle maiuscole e alle minuscole, dopodiché viene nuovamente filtrato sul caso del cliente in modo sensibile. Ovviamente, se ti capita di avere migliaia di voci che corrispondono senza distinzione tra maiuscole e minuscole, ma solo una di esse è quella corretta con distinzione tra maiuscole e minuscole, allora è molto sovraccarico. Ma non credo che la realtà presenterà tali scenari ... :)
Achim

1
@MassoodKhaari Quella soluzione che hai pubblicato renderebbe il case insensitive perché stai usando il case inferiore su entrambi i lati del confronto. L'OP necessita di un confronto case sensitive.
Jonny

1
"Pertanto, la soluzione migliore sarebbe cambiare le regole di confronto della colonna Nome nella tabella Thingies in COLLATE Latin1_General_CS_AS" - Non penso che questa sia la migliore. Il più delle volte ho bisogno di un filtro LIKE senza distinzione tra maiuscole e minuscole (.Contains ()), ma a volte dovrebbe essere sensibile al maiuscolo / minuscolo. Proverò la tua "soluzione lato client" - è molto più elegante per il mio caso d'uso credo (sarebbe bello capire cosa fa ma non puoi avere tutto :)).
L'incredibile gennaio

11

È possibile aggiungere l'annotazione [CaseSensitive] per EF6 + Code-first

Aggiungi queste classi

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

Modifica il tuo DbContext, aggiungi

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

Quindi fa

Add-Migration CaseSensitive

Aggiornare il database

basato sull'articolo https://milinaudara.wordpress.com/2015/02/04/case-sensitive-search-using-entity-framework-with-custom-annotation/ con alcune correzioni di bug


11

WHEREle condizioni in SQL Server non fanno distinzione tra maiuscole e minuscole per impostazione predefinita. Rendilo sensibile al maiuscolo / minuscolo modificando le regole di confronto predefinite della colonna ( SQL_Latin1_General_CP1_CI_AS) in SQL_Latin1_General_CP1_CS_AS.

Il modo fragile per farlo è con il codice. Aggiungi un nuovo file di migrazione e quindi aggiungilo all'interno del Upmetodo:

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

Ma

Puoi creare annotazioni personalizzate chiamate "CaseSensitive" utilizzando le nuove funzionalità EF6 e puoi decorare le tue proprietà in questo modo:

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

Questo post sul blog spiega come farlo.


In quell'articolo c'è un bug
RouR

3

La risposta data da @Morteza Manavi risolve il problema. Tuttavia, per una soluzione lato client , un modo elegante sarebbe il seguente (aggiungendo un doppio controllo).

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

-4

Mi è piaciuta la risposta di Morteza e normalmente preferirei risolvere il problema sul lato server. Per lato client normalmente utilizzo:

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

Fondamentalmente, prima controlla se c'è un utente con i criteri richiesti, quindi controlla se la password è la stessa. Un po 'prolisso, ma sento che è più facile da leggere quando ci possono essere un sacco di criteri coinvolti.


2
Questa risposta implica che stai memorizzando le password come testo normale nel tuo database, il che rappresenta un'enorme vulnerabilità di sicurezza.
Jason Coyne

2
@JasonCoyne La password con cui sta confrontando potrebbe già essere sottoposta ad hashing
Peter Morris,

-4

Nessuno dei due ha StringComparison.IgnoreCasefunzionato per me. Ma questo ha fatto:

context.MyEntities.Where(p => p.Email.ToUpper().Equals(muser.Email.ToUpper()));

2
Ciò non aiuterebbe con la domanda che è stata posta, ovvero:How can I achieve case sensitive comparison
Modifica registrazione

-4

Usa string.Equals

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

Inoltre, non devi preoccuparti di null e ottenere solo le informazioni che desideri.

Usa StringComparision.CurrentCultureIgnoreCase per Case Insensitive.

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

Equals () non può essere convertito in SQL ... Inoltre, se si tenta di utilizzare il metodo dell'istanza, StringComparison viene ignorato.
LMK

Hai provato questa soluzione? Ho provato a farlo lavorando bene con EF.
Darshan Joshi

-6

Non sono sicuro di EF4, ma EF5 supporta questo:

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

Curioso cosa genera sql.
Ronnie Overby

L'ho controllato con EF5, ha semplicemente generato un WHERE ... = ... in SQL. Quindi, di nuovo, questo dipende dalle impostazioni di confronto sul lato server SQL.
Achim

Anche con un confronto con distinzione tra maiuscole e minuscole nel DB non sono riuscito a fare in modo che questo o altri StringComparisonenum facessero la differenza. Ho visto abbastanza persone che suggeriscono questo genere di cose dovrebbe funzionare a pensare che il problema è da qualche parte nel file EDMX (db-prima), anche se stackoverflow.com/questions/841226/...
drzaus
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.