web-dev-qa-db-fra.com

Comparaison sensible à la casse LINQ to Entities

Ce n'est pas une comparaison sensible à la casse dans LINQ aux entités:

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

Comment puis-je effectuer une comparaison sensible à la casse avec LINQ to Entities?

110
Ronnie Overby

En effet, vous utilisez LINQ To Entities , qui convertit vos expressions Lambda en instructions SQL. Cela signifie que la sensibilité à la casse est à la merci de votre serveur SQL qui possède par défaut SQL_Latin1_General_CP1_CI_AS Collation et ce n'est pas sensible à la casse.

Utiliser ObjectQuery.ToTraceString pour afficher la requête SQL générée qui a été soumise à SQL Server révèle le mystère:

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

Lorsque vous créez une requête LINQ to Entities , LINQ to Entities exploite l’analyseur LINQ pour commencer le traitement de la requête et la convertir en une Arbre d'expression LINQ. L’arbre d’expression LINQ est ensuite transmis à l’API Services d’objet, qui convertit l'arborescence d'expression en une arborescence de commandes. Il est ensuite envoyé au fournisseur de magasin (par exemple, SqlClient), qui convertit l’arborescence de commandes en texte de commande de la base de données native. La requête est exécutée sur le magasin de données et les résultats sont matérialisés en objets d'entité par services d'objets . Aucune logique n'a été mise en place pour prendre en compte la sensibilité à la casse. Ainsi, quel que soit le cas que vous placez dans votre prédicat, il sera toujours traité de la même manière par votre serveur SQL Server, sauf si vous modifiez vos assemblages SQL Server pour cette colonne.

Solution côté serveur:

Par conséquent, la meilleure solution serait de modifier le classement de la colonne Nom dans la table Thingies en COLLATE Latin1_General_CS_AS qui est sensible à la casse en l'exécutant sur votre serveur SQL:

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

Pour plus d'informations sur les assemblages de SQL Server , jetez un œil à SQL SERVER Collate Case Sensitive Recherche de requête SQL

Solution côté client:

La seule solution que vous pouvez appliquer côté client consiste à utiliser LINQ to Objects pour effectuer une autre comparaison qui ne semble pas très élégante:

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

Vous pouvez ajouter une annotation [CaseSensitive] pour EF6 + Code-first

Ajouter cette classe

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

Modifiez votre DbContext, ajoutez

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

Alors fais

Add-Migration CaseSensitive

Base de données de mise à jour

basé sur l'article https://milinaudara.wordpress.com/2015/02/04/case-sensitive-search-using-entity-framework-with-custom-annotation/ avec une correction de bogue

10
RouR

Les conditions WHERE dans SQL Server ne sont pas sensibles à la casse par défaut. Faites-le sensible à la casse en modifiant les classements par défaut de la colonne (SQL_Latin1_General_CP1_CI_AS) à SQL_Latin1_General_CP1_CS_AS.

La manière fragile de faire ceci est avec du code. Ajoutez un nouveau fichier de migration, puis ajoutez-le dans la méthode Up:

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

Mais

Vous pouvez créer une annotation personnalisée appelée "CaseSensitive" à l'aide des nouvelles fonctionnalités EF6 et décorer vos propriétés de la manière suivante:

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

This blog post explique comment faire cela.

10
Milina Udara

La réponse donnée par @Morteza Manavi résout le problème. Néanmoins, pour un solution côté client, une manière élégante serait la suivante (ajout d’un double contrôle).

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