web-dev-qa-db-ja.com

LINQ to Entitiesの大文字と小文字を区別した比較

これは、LINQ to Entitiesの大文字と小文字を区別する比較ではありません。

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

LINQ to Entitiesと大文字と小文字を区別して比較するにはどうすればよいですか?

110
Ronnie Overby

これは、Lambda式を最終的にSQLステートメントに変換するLINQ To Entitiesを使用しているためです。つまり、大文字と小文字の区別は、デフォルトでSQL_Latin1_General_CP1_CI_ASを持っているSQL Serverに左右され、照合は大文字と小文字を区別しません。

ObjectQuery.ToTraceStringを使用して、実際にSQL Serverに送信された生成されたSQLクエリを確認すると、謎が明らかになります。

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

LINQ to Entitiesクエリを作成すると、LINQ to EntitiesはLINQパーサーを活用してクエリの処理を開始し、LINQ式ツリーに変換します。次に、LINQ式ツリーがObject ServicesAPIに渡されます。このAPIは、式ツリーをコマンドツリーに変換します。次に、ストアプロバイダー(たとえば、SqlClient)に送信され、コマンドツリーがネイティブデータベースコマンドテキストに変換されます。クエリはデータストアで実行され、その結果はMaterializedからEntity Objects by Object Servicesになります。大文字と小文字の区別を考慮に入れるためのロジックは間にありません。そのため、述語にどんなケースを入れても、その列のSQL Server Collat​​esを変更しない限り、SQL Serverは常に同じものとして扱います。

サーバー側のソリューション:

したがって、最善の解決策は、ThingiesテーブルのName列の照合順序をCOLLATELatin1_General_CS_ASに変更することです。 SQL Serverでこれを実行することにより:

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

SQL Server Collat​​esの詳細については、 SQL SERVER Collat​​e Case Sensiitive SQL Query Search をご覧ください。

クライアント側のソリューション:

クライアント側で適用できる唯一の解決策は、LINQ to Objectsを使用して、非常にエレガントではないように思われるさらに別の比較を行うことです。

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

EF6 +コードファーストの[CaseSensitive]注釈を追加できます

このクラスを追加

[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/ バグ修正付き

10
RouR

SQL ServerのWHERE条件は、デフォルトでは大文字と小文字を区別しません。列のデフォルトの照合順序(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");
}

しかし

新しいEF6機能を使用して「CaseSensitive」というカスタムアノテーションを作成し、次のようにプロパティを装飾できます。

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

これは ブログ投稿 でその方法を説明しています。

10
Milina Udara

@Morteza Manaviの回答が問題を解決します。それでも、クライアント側のソリューションの場合、エレガントな方法は次のようになります(二重チェックを追加します)。

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