c# - 什么.NET StringComparer 相当于 SQL 的 Latin1_General_CI_AS

标签 c# .net sql-server collation stringcomparer

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

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

一个真正简化的例子:

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

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

如果 StringComparer 说两个键 A 和 B 相等,而数据库认为它们是不同的,那么数据库中可能有两行具有这两个键,但如果被询问,缓存将阻止返回第二个键连续获取 A 和 B - 因为 B 的获取将错误地命中缓存并返回为 A 检索到的对象。

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

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

(旁注 - 缓存层是通用的,所以我不能对 key 的性质以及合适的排序规则做出特定假设。我数据库中的所有表都共享相同的默认服务器排序规则。我只是需要匹配存在的排序规则)

最佳答案

我最近遇到了同样的问题:我需要一个 IEqualityComparer<string>以类似 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 受此限制,但您应该了解问题出在哪里。 要为带有变音符号的字符串获得相同的哈希码,您可以执行以下操作:create implementationIEqualityComparer<T>实现GetHashCode这将调用适当的 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<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));
        }
    }

输出是:

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

我知道它看起来像 hack,但在检查反编译的 .NET 代码后,我不确定是否有任何其他选项以防需要通用功能。 因此请确保您不会陷入使用这个不完全正确的 API 的陷阱。
更新:
我还创建了 the gist with potential implementation of "SQL-like comparer"使用 CollationInfo . 还有就是要引起足够的重视where to search for "string pitfalls"在你的代码库中,所以如果字符串比较、哈希码、相等性应该更改为“类似 SQL 排序规则”,这些地方 100% 会被破坏,所以你必须找出并检查所有可能被破坏的地方.
更新#2:
有更好更简洁的方法让 GetHashCode() 处理 CompareOptions。有类 SortKey与 CompareOptions 一起正常工作,可以使用

检索

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

这是 link .NET 源代码和实现。

更新 #3:
如果您使用的是 .NET Framework 4.7.1+,则应使用新的 GlobalizationExtensions class正如 this recent answer 所提议的那样.

关于c# - 什么.NET StringComparer 相当于 SQL 的 Latin1_General_CI_AS,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/9384642/

相关文章:

sql-server - 如何通过 SQL Server xp_cmdshell 在 Windows 命令 shell 中根据创建日期加载文件

c# - Azure 服务总线队列

c# - .NET 中的反向排序字典

c# - 我的实体模型中应该有外键列吗?

c# - 如何使用 Microsoft 的 EWS API 从联系人获取/设置扩展属性?

sql - 创建与 SQL Server 的所有可能组合

c# - 编写 Windows 文件管理器应用程序的最佳编程语言

c# - 在 SslStream 中禁用不安全的重新协商

.net - WPF 列表框空数据模板

c# - 映射和sql数据库创建