LINQ กับเอนทิตีการเปรียบเทียบแบบคำนึงถึงขนาดตัวพิมพ์


115

นี่ไม่ใช่การเปรียบเทียบแบบคำนึงถึงขนาดตัวพิมพ์ใน LINQ กับเอนทิตี:

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

ฉันจะเปรียบเทียบกรณีที่สำคัญกับ LINQ กับเอนทิตีได้อย่างไร


@ รอนนี่: แน่ใจเหรอ? คุณหมายถึงการเปรียบเทียบแบบไม่คำนึงถึงขนาดตัวพิมพ์หรือไม่?
Michael Petrotta

14
อย่างแน่นอน. ไม่ฉันไม่ได้หมายความอย่างนั้น
Ronnie Overby

12
ไม่บนคอมพิวเตอร์ของฉันที่ใช้ EF 4.0 w / SQL Server 2008 R2 ข้างต้นไม่คำนึงถึงตัวพิมพ์เล็กและใหญ่ ฉันรู้ว่าหลายแห่งบอกว่า EF เป็นค่าเริ่มต้นที่คำนึงถึงตัวพิมพ์เล็กและใหญ่ แต่นั่นไม่ใช่สิ่งที่ฉันเคยพบ
tster

3
สิ่งนั้นจะไม่ขึ้นอยู่กับฐานข้อมูลพื้นฐานหรือไม่?
codymanix

1
@codymanix: นั่นเป็นคำถามที่ดี! Linq เป็น EF แปลนิพจน์แลมบ์ดาสำหรับแบบสอบถาม DB หรือไม่ ฉันไม่รู้คำตอบ
Tergiver

คำตอบ:


163

นั่นเป็นเพราะคุณใช้LINQ To Entitiesซึ่งท้ายที่สุดแล้วจะแปลงนิพจน์ Lambda ของคุณเป็นคำสั่ง SQL นั่นหมายความว่าความไวของตัวพิมพ์จะขึ้นอยู่กับความเมตตาของ SQL Server ของคุณซึ่งโดยค่าเริ่มต้นมีการเรียงลำดับSQL_Latin1_General_CP1_CI_ASและไม่คำนึงถึงขนาดตัวพิมพ์

ใช้ObjectQuery ToTraceStringเพื่อดูแบบสอบถาม SQL ที่สร้างขึ้นซึ่งถูกส่งไปยัง SQL Server จริงเผยให้เห็นความลึกลับ:

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

เมื่อคุณสร้างไฟล์ LINQ เพื่อองค์กรแบบสอบถามLINQ กับหน่วยงานยกระดับ parser LINQ ที่จะเริ่มต้นการประมวลผลแบบสอบถามและแปลงเป็นต้นไม้แสดงออก LINQ จากนั้นแผนภูมินิพจน์ LINQ จะถูกส่งไปยังObject Services API ซึ่งแปลงโครงสร้างนิพจน์เป็นแผนผังคำสั่ง จากนั้นจะส่งไปยังผู้ให้บริการร้านค้า (เช่น SqlClient) ซึ่งจะแปลงโครงสร้างคำสั่งเป็นข้อความคำสั่งฐานข้อมูลดั้งเดิม แบบสอบถามได้รับการดำเนินการในการจัดเก็บข้อมูลและผลจะMaterializedเข้าEntity วัตถุโดยบริการวัตถุ. ไม่มีการใส่ตรรกะระหว่างเพื่อพิจารณาความอ่อนไหวของตัวพิมพ์ ดังนั้นไม่ว่าคุณจะใส่เพรดิเคตในกรณีใดก็จะถือว่า SQL Server ของคุณเหมือนกันเสมอเว้นแต่คุณจะเปลี่ยน SQL Server Collates สำหรับคอลัมน์นั้น

โซลูชันฝั่งเซิร์ฟเวอร์:

ดังนั้นทางออกที่ดีที่สุดคือการเปลี่ยนการเรียงชื่อคอลัมน์ในตารางThingiesเป็น COLLATE Latin1_General_CS_ASซึ่งพิจารณาตัวพิมพ์เล็กและใหญ่โดยการรันสิ่งนี้บน SQL Server ของคุณ:

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

สำหรับข้อมูลเพิ่มเติมเกี่ยวกับSQL Server Collatesโปรดดูที่SQL SERVER Collate Case Sensitive SQL Query Search

โซลูชันฝั่งไคลเอ็นต์:

ทางออกเดียวที่คุณสามารถใช้ได้กับฝั่งไคลเอ็นต์คือการใช้LINQ กับ Objectsเพื่อทำการเปรียบเทียบอีกแบบหนึ่งซึ่งดูเหมือนจะไม่สวยหรูมากนัก:

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

ฉันกำลังสร้างสคีมาฐานข้อมูลด้วย Entity Framework ดังนั้นวิธีแก้ปัญหาโดยใช้รหัสการโทรของฉันจะดีที่สุด ฉันเดาว่าฉันจะทำการตรวจสอบหลังจากผลลัพธ์กลับมา ขอบคุณ
Ronnie Overby

ไม่มีปัญหา. ใช่ถูกต้องและฉันได้อัปเดตคำตอบของฉันด้วยโซลูชันฝั่งไคลเอ็นต์ แต่มันไม่ได้สวยงามมากนักและฉันยังคงแนะนำให้ใช้โซลูชันที่เก็บข้อมูล
Morteza Manavi

18
@eglasius สิ่งนี้ไม่เป็นความจริงอย่างสมบูรณ์: มันไม่ดึงข้อมูลทั้งหมด แต่จะดึงเฉพาะข้อมูลที่ตรงกับตัวพิมพ์เล็กและใหญ่เท่านั้นและหลังจากนั้นจะถูกกรองอีกครั้งในกรณีไคลเอนต์อย่างละเอียดอ่อน แน่นอนว่าหากคุณมีรายการหลายพันรายการที่ตรงกับตัวพิมพ์เล็กและใหญ่ แต่มีเพียงรายการเดียวเท่านั้นที่เป็นตัวพิมพ์เล็กและใหญ่ที่ถูกต้องก็จะมีค่าใช้จ่ายจำนวนมาก แต่ฉันไม่คิดว่าความเป็นจริงจะนำเสนอสถานการณ์เช่นนี้ ... :)
Achim

1
@MassoodKhaari วิธีแก้ปัญหาที่คุณโพสต์จะทำให้ไม่คำนึงถึงตัวพิมพ์เล็กและใหญ่เพราะคุณกำลังลดทั้งสองด้านของการเปรียบเทียบ OP ต้องการการเปรียบเทียบที่ละเอียดอ่อน
จอนนี่

1
"ดังนั้นทางออกที่ดีที่สุดคือการเปลี่ยนการเรียงคอลัมน์ชื่อในตาราง Thingies เป็น COLLATE Latin1_General_CS_AS" - ฉันไม่คิดว่านี่จะดีที่สุด ส่วนใหญ่ฉันต้องการตัวกรอง LIKE ที่ไม่คำนึงถึงตัวพิมพ์เล็กและใหญ่ (.Contains ()) แต่บางครั้งก็ควรคำนึงถึงตัวพิมพ์เล็กและใหญ่ ฉันจะลอง "โซลูชันฝั่งไคลเอ็นต์" ของคุณ - มันดูดีกว่ามากสำหรับกรณีการใช้งานของฉันฉันคิดว่า (คงจะดีถ้าเข้าใจว่ามันทำอะไรได้บ้าง แต่คุณไม่สามารถมีได้ทั้งหมด :))
เหลือเชื่อ

11

คุณสามารถเพิ่มคำอธิบายประกอบ [CaseSensitive] สำหรับ EF6 + Code-first

เพิ่มชั้นเรียนนี้

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

แล้วทำ

Add-Migration CaseSensitive

ปรับปรุงฐานข้อมูล

อ้างอิงจากบทความhttps://milinaudara.wordpress.com/2015/02/04/case-sensitive-search-using-entity-framework-with-custom-annotation/พร้อมการแก้ไขข้อบกพร่องบางอย่าง


11

WHEREเงื่อนไขใน SQL Server ไม่คำนึงถึงขนาดตัวพิมพ์โดยค่าเริ่มต้น ทำให้มันเป็นกรณีที่สำคัญโดยการเปลี่ยน collations เริ่มต้นของคอลัมน์ ( 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");
}

แต่

คุณสามารถสร้างคำอธิบายประกอบแบบกำหนดเองที่เรียกว่า "CaseSensitive" โดยใช้คุณสมบัติใหม่ของ EF6 และคุณสามารถตกแต่งคุณสมบัติของคุณได้ดังนี้:

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

นี้โพสต์บล็อกอธิบายถึงวิธีการทำเช่นนั้น


ในบทความนั้นมีข้อบกพร่อง
RouR

3

คำตอบที่ได้รับจาก @Morteza Manavi ช่วยแก้ปัญหาได้ อย่างไรก็ตามสำหรับโซลูชันฝั่งไคลเอ็นต์วิธีที่ดีจะเป็นดังต่อไปนี้ (เพิ่มการตรวจสอบอีกครั้ง)

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

-4

ฉันชอบคำตอบของ Morteza และโดยปกติแล้วจะชอบแก้ไขที่ฝั่งเซิร์ฟเวอร์ สำหรับฝั่งไคลเอ็นต์ฉันมักใช้:

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

โดยทั่วไปก่อนอื่นให้ตรวจสอบว่ามีผู้ใช้ที่มีเกณฑ์ที่กำหนดหรือไม่จากนั้นตรวจสอบว่ารหัสผ่านเหมือนกันหรือไม่ ยืดเยื้อไปหน่อย แต่ฉันรู้สึกว่ามันง่ายกว่าที่จะอ่านเมื่ออาจมีเกณฑ์ทั้งหมดที่เกี่ยวข้อง


2
คำตอบนี้หมายความว่าคุณกำลังจัดเก็บรหัสผ่านเป็นข้อความธรรมดาในฐานข้อมูลของคุณซึ่งเป็นช่องโหว่ด้านความปลอดภัยอย่างมาก
Jason Coyne

2
@JasonCoyne รหัสผ่านที่เขาเปรียบเทียบกับสามารถแฮชได้แล้ว
Peter Morris

-4

ทั้งสองอย่างไม่ได้ผลStringComparison.IgnoreCaseสำหรับฉัน แต่สิ่งนี้ทำ:

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

2
สิ่งนี้จะไม่ช่วยอะไรกับคำถามที่ถูกถามนั่นคือHow can I achieve case sensitive comparison
Reg Edit

-4

ใช้ string.Equals

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

นอกจากนี้คุณไม่ต้องกังวลเกี่ยวกับค่าว่างและรับเฉพาะข้อมูลที่คุณต้องการกลับมา

ใช้ StringComparision.CurrentCultureIgnoreCase สำหรับ Case Insensitive

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

ไม่สามารถแปลง Equals () เป็น SQL ได้ ... นอกจากนี้หากคุณลองและใช้วิธีการอินสแตนซ์การเปรียบเทียบ StringComparison จะถูกละเว้น
LMK

คุณได้ลองใช้วิธีนี้แล้วหรือยัง? ฉันได้ลองสิ่งนี้ในตอนท้ายเพื่อทำงานร่วมกับ EF ได้ดี
Darshan Joshi

-6

ไม่แน่ใจเกี่ยวกับ EF4 แต่ EF5 รองรับสิ่งนี้:

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

อยากรู้ว่า sql ที่สร้างอะไร
Ronnie Overby

ฉันตรวจสอบสิ่งนี้ด้วย EF5 มันสร้าง WHERE ... = ... ใน SQL อีกครั้งสิ่งนี้ขึ้นอยู่กับการตั้งค่าการจัดเรียงบนฝั่งเซิร์ฟเวอร์ SQL
Achim

แม้จะมีการเปรียบเทียบแบบตรงตามตัวพิมพ์เล็กและตัวพิมพ์ใหญ่ในฐานข้อมูลฉันก็ไม่สามารถหาสิ่งนี้หรือสิ่งอื่นใดStringComparisonมาสร้างความแตกต่างได้ ฉันเคยเห็นคนแนะนำเรื่องแบบนี้มามากพอแล้วคิดว่าปัญหาอยู่ที่ไหนสักแห่งในไฟล์ EDMX (db-first) แม้ว่าstackoverflow.com/questions/841226/…
drzaus
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.