2012-02-21 41 views
11

Tôi đang triển khai một lớp đệm giữa cơ sở dữ liệu của tôi và mã C# của tôi. Ý tưởng là lưu trữ kết quả của một số truy vấn DB nhất định dựa trên các tham số cho truy vấn. Cơ sở dữ liệu đang sử dụng collation mặc định - hoặc SQL_Latin1_General_CP1_CI_AS hoặc Latin1_General_CI_AS, mà tôi tin rằng dựa trên một số googling ngắn gọn là tương đương cho bình đẳng, chỉ khác nhau để phân loại.Cái gì NET StringComparer tương đương với SQL1 của Latin1_General_CI_AS

Tôi cần một .NET StringComparer có thể cho tôi cùng một hành vi, ít nhất là để kiểm tra bình đẳng và tạo mã băm, khi đối chiếu của cơ sở dữ liệu đang sử dụng. Mục đích là để có thể sử dụng StringComparer trong một từ điển .NET trong mã C# để xác định xem một chuỗi khóa cụ thể đã có trong bộ nhớ cache hay chưa.

Một ví dụ thực sự đơn giản:

var comparer = StringComparer.??? // What goes here? 

private static Dictionary<string, MyObject> cache = 
    new Dictionary<string, MyObject>(comparer); 

public static MyObject GetObject(string key) { 
    if (cache.ContainsKey(key)) { 
     return cache[key].Clone(); 
    } else { 
     // invoke SQL "select * from mytable where mykey = @mykey" 
     // with parameter @mykey set to key 
     MyObject result = // object constructed from the sql result 
     cache[key] = result; 
     return result.Clone(); 
    } 
} 
public static void SaveObject(string key, MyObject obj) { 
    // invoke SQL "update mytable set ... where mykey = @mykey" etc 
    cache[key] = obj.Clone(); 
} 

Lý do điều quan trọng là các StringComparer phù hợp đối chiếu của cơ sở dữ liệu là cả hai dương tính giả và âm tính giả sẽ có những ảnh hưởng xấu đối với các mã.

Nếu StringComparer nói rằng hai phím A và B bằng nhau khi cơ sở dữ liệu tin rằng chúng khác nhau, thì có thể có hai hàng trong cơ sở dữ liệu với hai khóa đó, nhưng bộ nhớ cache sẽ ngăn chặn thứ hai. yêu cầu cho A và B liên tiếp - bởi vì nhận được cho B sẽ không chính xác nhấn bộ nhớ cache và trả lại đối tượng đã được lấy cho A.

Vấn đề là tinh tế hơn nếu StringComparer nói rằng A và B là khác nhau khi cơ sở dữ liệu tin rằng chúng bình đẳng, nhưng không kém phần nào. Các cuộc gọi GetObject cho cả hai phím sẽ là tốt và trả về các đối tượng tương ứng với cùng một hàng cơ sở dữ liệu. Nhưng sau đó gọi SaveObject với khóa A sẽ để lại bộ nhớ cache không chính xác; vẫn sẽ có một mục nhập bộ nhớ cache cho khóa B có dữ liệu cũ. Một GetObject tiếp theo (B) sẽ cung cấp thông tin lỗi thời.

Vì vậy, để mã của tôi hoạt động chính xác, tôi cần StringComparer để phù hợp với hành vi của cơ sở dữ liệu để kiểm tra bình đẳng và tạo mã băm. Việc googling của tôi cho đến nay đã mang lại rất nhiều thông tin về thực tế rằng các đối chiếu SQL và so sánh .NET không chính xác tương đương, nhưng không có chi tiết về sự khác biệt là gì, cho dù chúng chỉ giới hạn trong các phân loại hoặc liệu có thể tìm thấy một StringComparer tương đương với cụ thể SQL collation nếu không cần một giải pháp đa năng.

(Lưu ý bên cạnh - lớp lưu trong bộ nhớ cache là mục đích chung, vì vậy tôi không thể đưa ra các giả định cụ thể về bản chất của khóa đó là gì và collation nào là phù hợp. Tất cả các bảng trong cơ sở dữ liệu của tôi đều chia sẻ cùng một máy chủ mặc định. chỉ cần khớp với đối chiếu khi nó tồn tại)

Trả lời

6

Hãy xem qua lớp CollationInfo. Nó nằm trong một hội đồng gọi là Microsoft.SqlServer.Management.SqlParser.dll mặc dù tôi không hoàn toàn chắc chắn nơi để có được điều này. Có một danh sách tĩnh gồm Collations (tên) và phương thức tĩnh GetCollationInfo (theo tên).

Mỗi CollationInfoComparer. Nó không chính xác giống như StringComparer nhưng có chức năng tương tự.

EDIT: Microsoft.SqlServer.Management.SqlParser.dll là một phần của gói Shared Management Objects (SMO).Tính năng này có thể được tải về cho SQL Server 2008 R2 ở đây:

http://www.microsoft.com/download/en/details.aspx?id=16978#SMO

EDIT:CollationInfo không có một tài sản mang tên EqualityComparer mà là một IEqualityComparer<string>.

+0

Thật không may IComparer không bao gồm khả năng nhận mã băm - tôi cần IEqualityComparer, cái mà StringComparer cung cấp. – Stuart

+0

@Stuart - Xem bản chỉnh sửa của tôi, CollactionInfo không có IEqualityComparer. – dana

+0

Có cách nào để đạt được tương đương .NET SQL_Latin1_General_CP1_CI_AS bằng cách (chỉ đơn giản) cấu hình hoặc triển khai một kiểu cơ bản không? SMO là một sự phụ thuộc nặng nề cho tình hình của tôi. –

1

Máy chủ SQL Server.GetStringComparer có thể được sử dụng.

+1

Thật không may IComparer không bao gồm khả năng để có được một hashcode - Tôi cần một IEqualityComparer đó là những gì StringComparer cung cấp. – Stuart

9

Gần đây tôi đã gặp phải sự cố tương tự: Tôi cần một IEqualityComparer<string> hoạt động theo kiểu giống SQL. Tôi đã thử CollationInfoEqualityComparer của nó. Nếu DB của bạn luôn là _AS (giọng nhạy cảm) thì giải pháp của bạn sẽ hoạt động, nhưng trong trường hợp nếu bạn thay đổi collation là AI hoặc WI hoặc bất kỳ "insensitive" khác thì băm sẽ bẻ gãy.
Tại sao? Nếu bạn dịch ngược Microsoft.SqlServer.Management.SqlParser.dll và nhìn vào bên trong bạn sẽ tìm ra rằng CollationInfo nội bộ sử dụng CultureAwareComparer.GetHashCode (đó là lớp bên trong của mscorlib.dll) và cuối cùng nó như sau:

public override int GetHashCode(string obj) 
{ 
    if (obj == null) 
    throw new ArgumentNullException("obj"); 
    CompareOptions options = CompareOptions.None; 
    if (this._ignoreCase) 
    options |= CompareOptions.IgnoreCase; 
    return this._compareInfo.GetHashCodeOfString(obj, options); 
} 

Như bạn có thể thấy nó có thể tạo ra cùng một mã băm cho "aa" và "AA", nhưng không cho "åå" và "aa" (giống nhau, nếu bạn bỏ qua dấu phụ (AI) trong phần lớn các nền văn hóa, vì vậy chúng nên có cùng mã băm). Tôi không biết tại sao .NET API bị giới hạn bởi điều này, nhưng bạn nên hiểu được vấn đề có thể đến từ đâu. Để nhận được cùng một mã băm cho chuỗi có dấu phụ, bạn có thể thực hiện các thao tác sau: create implementation của IEqualityComparer<T> thực hiện GetHashCode sẽ gọi cho đối tượng GetHashCodeOfString của đối tượng phù hợp vì phương pháp này là nội bộ và không thể sử dụng trực tiếp. Nhưng gọi đó là trực tiếp với đúng CompareOptions sẽ tạo ra kết quả mong muốn: Xem ví dụ này:

static void Main(string[] args) 
    { 
     const string outputPath = "output.txt"; 
     const string latin1GeneralCiAiKsWs = "Latin1_General_100_CI_AI_KS_WS"; 
     using (FileStream fileStream = File.Open(outputPath, FileMode.Create, FileAccess.Write)) 
     { 
      using (var streamWriter = new StreamWriter(fileStream, Encoding.UTF8)) 
      { 
       string[] strings = { "aa", "AA", "äå", "ÄÅ" }; 
       CompareInfo compareInfo = CultureInfo.GetCultureInfo(1033).CompareInfo; 
       MethodInfo GetHashCodeOfString = compareInfo.GetType() 
        .GetMethod("GetHashCodeOfString", 
        BindingFlags.Instance | BindingFlags.NonPublic, 
        null, 
        new[] { typeof(string), typeof(CompareOptions), typeof(bool), typeof(long) }, 
        null); 

       Func<string, int> correctHackGetHashCode = s => (int)GetHashCodeOfString.Invoke(compareInfo, 
        new object[] { s, CompareOptions.IgnoreCase | CompareOptions.IgnoreNonSpace, false, 0L }); 

       Func<string, int> incorrectCollationInfoGetHashCode = 
        s => CollationInfo.GetCollationInfo(latin1GeneralCiAiKsWs).EqualityComparer.GetHashCode(s); 

       PrintHashCodes(latin1GeneralCiAiKsWs, incorrectCollationInfoGetHashCode, streamWriter, strings); 
       PrintHashCodes("----", correctHackGetHashCode, streamWriter, strings); 
      } 
     } 
     Process.Start(outputPath); 
    } 
    private static void PrintHashCodes(string collation, Func<string, int> getHashCode, TextWriter writer, params string[] strings) 
    { 
     writer.WriteLine(Environment.NewLine + "Used collation: {0}", collation + Environment.NewLine); 
     foreach (string s in strings) 
     { 
      WriteStringHashcode(writer, s, getHashCode(s)); 
     } 
    } 

Đầu ra là:

Used collation: Latin1_General_100_CI_AI_KS_WS 
aa, hashcode: 2053722942 
AA, hashcode: 2053722942 
äå, hashcode: -266555795 
ÄÅ, hashcode: -266555795 

Used collation: ---- 
aa, hashcode: 2053722942 
AA, hashcode: 2053722942 
äå, hashcode: 2053722942 
ÄÅ, hashcode: 2053722942 

Tôi biết nó trông giống như hack, nhưng sau khi kiểm tra decompiled NET mã Tôi không chắc chắn nếu có bất kỳ tùy chọn khác trong trường hợp các chức năng chung là cần thiết. Vì vậy, hãy chắc chắn rằng bạn sẽ không rơi vào bẫy bằng cách sử dụng API này không hoàn toàn chính xác.
CẬP NHẬT:
Tôi cũng đã tạo the gist with potential implementation of "SQL-like comparer" sử dụng CollationInfo. Cũng cần chú ý đủ where to search for "string pitfalls" trong cơ sở mã của bạn, vì vậy nếu so sánh chuỗi, hashcode, bình đẳng nên được thay đổi thành "SQL collation-like" những địa điểm này là 100% sẽ bị hỏng, vì vậy bạn sẽ phải tìm hiểu và kiểm tra tất cả những nơi có thể bị phá vỡ.
CẬP NHẬT # 2:
Có cách tốt hơn và sạch hơn để làm cho GetHashCode() đối xử với CompareOptions. Có lớp SortKey mà làm việc một cách chính xác với CompareOptions và nó có thể được lấy ra sử dụng

CompareInfo.GetSortKey (yourString, yourCompareOptions) .GetHashCode()

Đây là link để NET mã nguồn và thực hiện.

+0

+1 Đó là một câu trả lời được nghiên cứu nghiêm túc! Tôi ước tôi có thể bầu bạn nhiều lần. –

+0

được giải thích một cách độc đáo. – user2250250

+0

Hãy chú ý đến CẬP NHẬT # 2 - không có cách nào để làm cho GetHashCode() hoạt động chính xác với CompareOptions. Đó là cách sạch hơn và không yêu cầu bất kỳ sự phản chiếu nào. Thật không may, tôi có thể tìm thấy nó chỉ một thời gian sau khi tôi đã thực hiện bài đăng này. –

Các vấn đề liên quan