什么.NET StringComparer等同于SQL的Latin1_General_CI_AS

我正在我的数据库和我的C#代码之间实现一个缓存层。 我们的想法是根据参数将某些数据库查询的结果缓存到查询中。 数据库正在使用默认排序SQL_Latin1_General_CP1_CI_ASSQL_Latin1_General_CP1_CI_ASLatin1_General_CI_AS ,我相信基于一些简短的谷歌搜索相当于相等,只是排序不同。

我需要一个.NET StringComparer,它可以给我相同的行为,至少对于相等测试和哈希码生成,正如数据库的排序规则所使用的那样。 目标是能够在C#代码中的.NET字典中使用StringComparer来确定特定字符串键是否已经在缓存中。

一个非常简单的例子:

 var comparer = StringComparer.??? // What goes here? private static Dictionary cache = new Dictionary(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(); } 

StringComparer与数据库的排序规则匹配很重要的原因是误报和漏报都会对代码产生不良影响。

如果StringComparer说当数据库认为它们是不同的时,两个密钥A和B是相等的,那么数据库中可能有两行带有这两个密钥,但是如果要求A和缓存,则缓存将阻止第二个密钥返回B连续 – 因为B的get将错误地命中缓存并返回为A检索的对象。

如果StringComparer在数据库认为它们相同时说A和B不同,那么问题会更加微妙,但问题就不多了。 对两个键的GetObject调用都没问题,并返回对应于同一数据库行的对象。 但是然后用密钥A调用SaveObject会使缓存不正确; 对于具有旧数据的密钥B,仍然存在缓存条目。 随后的GetObject(B)将提供过时的信息。

因此,为了使我的代码正常工作,我需要StringComparer来匹配数据库行为,以进行相等性测试和哈希码生成。 到目前为止,我的谷歌搜索已经产生了很多关于SQL排序规则和.NET比较不完全等同的事实的信息,但没有关于差异的详细信息,它们是否仅限于排序的差异,或者是否有可能找到如果不需要通用解决方案,则StringComparer等效于特定的 SQL排序规则。

(旁注 – 缓存层是通用的,因此我不能对密钥的性质和适当的归类做出特定的假设。我的数据库中的所有表共享相同的默认服务器排序规则。我只需要匹配存在的整理)

看一下CollationInfo类。 它位于一个名为Microsoft.SqlServer.Management.SqlParser.dll的程序集中,虽然我不完全确定从哪里得到它。 有一个静态列表的Collations (名称)和一个静态方法GetCollationInfo (按名称)。

每个CollationInfo都有一个Comparer 。 它与StringComparer不完全相同,但具有类似的function。

编辑: Microsoft.SqlServer.Management.SqlParser.dll是共享管理对象(SMO)包的一部分。 可以在此处下载SQL Server 2008 R2的此function:

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

编辑: CollationInfo有一个名为EqualityComparer的属性,它是一个IEqualityComparer

我最近遇到了同样的问题:我需要一个IEqualityComparer ,它的行为类似于SQL。 我已经尝试过CollationInfo及其EqualityComparer 。 如果您的数据库始终是_AS (重音敏感),那么您的解决方案将起作用,但如果您更改了AIWI或其他“不敏感”的排序规则,则散列将会中断。
为什么? 如果您反编译Microsoft.SqlServer.Management.SqlParser.dll并查看内部,您会发现CollationInfo内部使用CultureAwareComparer.GetHashCode (它是mscorlib.dll的内部类),最后它执行以下操作:

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

正如你所看到的,它可以为“aa”和“AA”生成相同的哈希码,但不能用于“äå”和“aa”(如果你在大多数文化中忽略变音符号(AI),它们是相同的,所以它们应该具有相同的哈希码)。 我不知道为什么.NET API受此限制,但您应该了解问题的来源。 要为具有变音符号的字符串获取相同的哈希码,您可以执行以下操作: 创建 IEqualityComparer实现,实现GetHashCode ,它将通过reflection调用相应的CompareInfo的对象的GetHashCodeOfString ,因为此方法是内部的,不能直接使用。 但是使用正确的CompareOptions直接调用它将产生所需的结果:请参阅此示例:

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

输出是:

 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 

我知道它看起来像黑客,但在检查反编译的.NET代码后,我不确定是否有任何其他选项,以防需要通用function。 所以请确保使用这个不完全正确的API不会陷入陷阱。
更新:
我还使用CollationInfo创建了可能实现“类似SQL的比较器”的要点 。 另外应该注意在你的代码库中搜索“字符串陷阱” ,所以如果字符串比较,hashcode,相等应该改为“SQL collat​​ion-like”那些地方是100%会被打破,所以你我必须找出并检查所有可以破坏的地方。
更新#2:
有更好更清洁的方法使GetHashCode()处理CompareOptions。 SortKey类与CompareOptions一起正常工作,可以使用它来检索

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

这是.NET源代码和实现的链接 。

SQL Server的Server.GetStringComparer可能有一些用处。