LINQ dengan perbandingan peka huruf besar kecil Entitas

115

Ini bukan perbandingan peka huruf besar kecil di LINQ dengan Entitas:

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

Bagaimana cara saya mencapai perbandingan case sensitive dengan LINQ ke Entitas?

Ronnie Overby
sumber
@Ronnie: apakah kamu yakin tentang itu? Apakah yang Anda maksud perbandingan tidak peka huruf besar / kecil ?
Michael Petrotta
14
Sangat yakin. Tidak, aku tidak bermaksud begitu.
Ronnie Overby
12
Tidak, di komputer saya yang menjalankan EF 4.0 w / SQL Server 2008 R2, hal di atas tidak peka huruf besar / kecil. Saya tahu banyak tempat mengatakan bahwa EF peka huruf besar / kecil, tapi bukan itu yang saya alami.
tster
3
Bukankah itu tergantung pada database yang mendasarinya?
codymanix
1
@codymanix: Itu pertanyaan yang bagus! Apakah Linq ke EF menerjemahkan ekspresi lambda untuk kueri DB? Saya tidak tahu jawabannya.
Tergiver

Jawaban:

163

Itu karena Anda menggunakan LINQ To Entities yang pada akhirnya mengubah ekspresi Lambda Anda menjadi pernyataan SQL. Itu berarti sensitivitas huruf besar / kecil berada di tangan SQL Server Anda yang secara default memiliki Penyusunan SQL_Latin1_General_CP1_CI_AS dan TIDAK peka huruf besar / kecil.

Menggunakan ObjectQuery.ToTraceString untuk melihat kueri SQL yang dihasilkan yang sebenarnya telah dikirim ke SQL Server mengungkap misteri:

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

Saat Anda membuat kueri LINQ ke Entitas , LINQ ke Entitas memanfaatkan pengurai LINQ untuk mulai memproses kueri dan mengubahnya menjadi pohon ekspresi LINQ. Pohon ekspresi LINQ kemudian diteruskan ke Object Services API, yang mengubah pohon ekspresi menjadi pohon perintah. Ini kemudian dikirim ke penyedia penyimpanan (misalnya SqlClient), yang mengubah pohon perintah menjadi teks perintah database asli. Query dieksekusi di penyimpanan data dan hasilnya direalisasikan menjadi Objek Entitas oleh Object Services. Tidak ada logika yang dimasukkan untuk mempertimbangkan sensitivitas huruf. Jadi, apa pun kasus yang Anda masukkan ke dalam predikat, itu akan selalu diperlakukan sama oleh SQL Server Anda kecuali jika Anda mengubah SQL Server Collates untuk kolom itu.

Solusi sisi server:

Oleh karena itu, solusi terbaik adalah dengan mengubah pemeriksaan dari Nama kolom di thingies meja ke Collate Latin1_General_CS_AS yang case sensitive dengan menjalankan ini pada SQL Server Anda:

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

Untuk informasi selengkapnya tentang SQL Server Collates , lihat SQL SERVER Collate Case Sensitive SQL Query Search

Solusi sisi klien:

Satu-satunya solusi yang dapat Anda terapkan di sisi klien adalah dengan menggunakan LINQ ke Objek untuk melakukan perbandingan lain yang tampaknya tidak terlalu elegan:

Thingies.Where(t => t.Name == "ThingamaBob")
        .AsEnumerable()
        .First(t => t.Name == "ThingamaBob");
Morteza Manavi
sumber
Saya membuat skema database dengan Entity Framework, jadi solusi yang terbaik adalah menggunakan kode panggilan saya. Saya kira saya akan melakukan pemeriksaan setelah hasilnya keluar. Terima kasih.
Ronnie Overby
Tidak masalah. Ya, itu benar dan saya telah memperbarui jawaban saya dengan solusi sisi klien, namun tidak terlalu elegan dan saya tetap merekomendasikan untuk menggunakan solusi penyimpanan data.
Morteza Manavi
18
@eglasius Ini tidak sepenuhnya benar: Ia tidak mengambil SEMUA data, ia hanya mengambil data yang cocok dengan huruf kecil dan tidak peka, dan setelah itu disaring lagi pada kasus klien secara sensitif. Tentu saja, jika Anda kebetulan memiliki ribuan entri yang tidak peka huruf besar / kecil, tetapi hanya salah satunya yang benar peka huruf besar / kecil, maka itu banyak overhead. Tapi saya tidak berpikir bahwa kenyataan akan menghadirkan skenario seperti itu ... :)
Achim
1
@MassoodKhaari Solusi yang Anda posting akan membuatnya menjadi Case Insensitive karena Anda menurunkan casing kedua sisi perbandingan. OP membutuhkan perbandingan yang peka huruf besar / kecil.
Jonny
1
"Oleh karena itu, solusi terbaik adalah mengubah susunan kolom Nama di tabel Thingies menjadi COLLATE Latin1_General_CS_AS" - Menurut saya ini bukan yang terbaik. Sebagian besar waktu saya perlu case insensitive LIKE filter (.Contains ()) tetapi terkadang harus case sensitive. Saya akan mencoba "solusi sisi klien" Anda - ini jauh lebih elegan untuk kasus penggunaan saya, saya pikir (akan menyenangkan untuk memahami apa yang dilakukannya tetapi Anda tidak dapat memiliki semuanya :)).
Tanggal
11

Anda dapat menambahkan anotasi [CaseSensitive] untuk EF6 + Code-first

Tambahkan kelas ini

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

Ubah DbContext Anda, tambahkan

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

Lalu lakukan

Add-Migration CaseSensitive

Perbarui-Database

berdasarkan artikel https://milinaudara.wordpress.com/2015/02/04/case-sensitive-search-using-entity-framework-with-custom-annotation/ dengan beberapa perbaikan bug

RouR
sumber
11

WHEREkondisi di SQL Server tidak membedakan huruf besar / kecil secara default. Buat peka huruf besar kecil dengan mengubah susunan default kolom ( SQL_Latin1_General_CP1_CI_AS) menjadi SQL_Latin1_General_CP1_CS_AS.

Cara yang rapuh untuk melakukannya adalah dengan kode. Tambahkan file migrasi baru dan kemudian tambahkan ini di dalam Upmetode:

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

Tapi

Anda dapat membuat anotasi khusus yang disebut "CaseSensitive" menggunakan fitur EF6 yang baru dan Anda dapat menghias properti Anda seperti ini:

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

Posting blog ini menjelaskan bagaimana melakukan itu.

Milina Udara
sumber
Dalam artikel itu ada bug
RouR
3

Jawaban yang diberikan oleh @Morteza Manavi menyelesaikan masalah tersebut. Namun, untuk solusi sisi klien , cara yang elegan adalah sebagai berikut (menambahkan tanda centang ganda).

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

Saya menyukai jawaban Morteza, dan biasanya lebih suka memperbaiki di sisi server. Untuk sisi klien saya biasanya menggunakan:

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

Pada dasarnya, pertama-tama periksa apakah ada pengguna dengan kriteria yang disyaratkan, lalu periksa apakah kata sandinya sama. Agak bertele-tele, tetapi saya merasa lebih mudah untuk membaca ketika mungkin ada banyak kriteria yang terlibat.

Rune Borgen
sumber
2
Jawaban ini menyiratkan bahwa Anda menyimpan kata sandi sebagai teks biasa dalam database Anda yang merupakan kerentanan keamanan yang sangat besar.
Jason Coyne
2
@JasonCoyne Kata sandi yang dia bandingkan sudah bisa di-hash
Peter Morris
-4

Tak satu pun dari yang StringComparison.IgnoreCaseberhasil untuk saya. Tapi ini berhasil:

context.MyEntities.Where(p => p.Email.ToUpper().Equals(muser.Email.ToUpper()));
saquib adil
sumber
2
Ini tidak akan membantu dengan pertanyaan yang telah diajukan, yaitu,How can I achieve case sensitive comparison
Reg Edit
-4

Gunakan string. Sama

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

Selain itu, Anda tidak perlu khawatir tentang null dan hanya mendapatkan kembali informasi yang Anda inginkan.

Gunakan StringComparision.CurrentCultureIgnoreCase untuk Case Insensitive.

Thingies.First(t => string.Equals(t.Name, "ThingamaBob", StringComparison.CurrentCultureIgnoreCase);
Darshan Joshi
sumber
Equals () tidak dapat dikonversi ke SQL ... Juga jika Anda mencoba dan menggunakan metode contoh, Perbandingan String akan diabaikan.
LMK
Sudahkah Anda mencoba solusi ini? Saya mencoba ini pada akhir saya bekerja dengan baik dengan EF.
Darshan Joshi
-6

Tidak yakin tentang EF4, tetapi EF5 mendukung ini:

Thingies
    .First(t => t.Name.Equals(
        "ThingamaBob",
        System.StringComparison.InvariantCultureIgnoreCase)
bloparod.dll
sumber
Penasaran sql apa yang dihasilkan.
Ronnie Overby
Saya memeriksa ini dengan EF5, itu hanya menghasilkan WHERE ... = ... dalam SQL. Jadi sekali lagi, ini tergantung pada pengaturan pemeriksaan di sisi server SQL.
Achim
Bahkan dengan pemeriksaan case-sensitive di DB, saya tidak bisa mendapatkan StringComparisonenum ini atau yang lainnya untuk membuat perbedaan. Saya telah melihat cukup banyak orang yang menyarankan hal semacam ini harus bekerja untuk berpikir masalahnya ada di suatu tempat di file EDMX (db-first), meskipun stackoverflow.com/questions/841226/…
drzaus