13

データベースと C# コードの間にキャッシュ レイヤーを実装しています。アイデアは、クエリへのパラメーターに基づいて特定の DB クエリの結果をキャッシュすることです。データベースはデフォルトの照合順序を使用しています -SQL_Latin1_General_CP1_CI_ASまたはLatin1_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 がデータベースの照合順序と一致することが重要な理由は、偽陽性と偽陰性の両方がコードに悪影響を与えるためです。

2 つのキー A と B が異なるとデータベースが認識しているときに StringComparer がこれらのキーが等しいと言う場合、データベースにはこれら 2 つのキーを持つ 2 つの行が存在する可能性がありますが、A と B を要求された場合に 2 番目のキーが返されるのをキャッシュが防ぎます。 B の連続 - B の get が誤ってキャッシュにヒットし、A に対して取得されたオブジェクトを返すためです。

データベースが A と B が等しいと認識しているときに StringComparer が A と B が異なると言う場合、問題はより微妙ですが、それほど問題ではありません。両方のキーに対する GetObject 呼び出しは問題なく、同じデータベース行に対応するオブジェクトを返します。しかし、キー A を指定して SaveObject を呼び出すと、キャッシュが正しくないままになります。古いデータを持つキー B のキャッシュ エントリがまだ存在します。後続の GetObject(B) は古い情報を提供します。

したがって、私のコードが正しく機能するためには、 StringComparer が等価テストとハッシュコード生成のデータベースの動作に一致する必要があります。これまでの私のグーグル検索では、SQL 照合と .NET 比較が完全に同等ではないという事実について多くの情報が得られましたが、違いが何であるか、並べ替えの違いのみに限定されているかどうか、または見つけることが可能かどうかについての詳細はありません。汎用ソリューションが必要ない場合は、特定のSQL 照合順序と同等の StringComparer 。

(補足: キャッシング レイヤーは汎用的なものであるため、キーの性質や適切な照合順序について特定の仮定を立てることはできません。データベース内のすべてのテーブルは、同じ既定のサーバー照合順序を共有しています。一致する必要があるだけです。存在する照合順序)

4

4 に答える 4

13

私は最近、同じ問題に直面しました: IEqualityComparer<string>SQL のようなスタイルで動作する が必要です。私は試してみCollationInfoましたEqualityComparer。DB が常に_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 がこれによって制限される理由はわかりませんが、問題の原因を理解する必要があります。分音符号のある文字列に対して同じハッシュコードを取得するには、次のようにします:このメソッドは内部的であり、直接使用できないため、リフレクションを介して適切なのオブジェクトを呼び出す実装の実装を作成します。しかし、これを直接 correct で呼び出すと、望ましい結果が得られます: 次の例を参照してください:IEqualityComparer<T>GetHashCodeCompareInfoGetHashCodeOfStringCompareOptions

    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

ハッキングのように見えることはわかっていますが、逆コンパイルされた .NET コードを調べた後、汎用機能が必要な場合に備えて他のオプションがあるかどうかはわかりません。したがって、この完全に正しくない API を使用して罠に陥らないように注意してください。
更新: .
_ _ また、コードベースのどこに「文字列の落とし穴」CollationInfoがあるか十分に注意する必要があります。そのため、文字列比較、ハッシュコード、等価性を「SQL 照合のような」ものに変更すると、それらの場所は 100% 壊れます。壊れる可能性のあるすべての場所を見つけて検査する必要があります。更新#2:

GetHashCode() が CompareOptions を処理するようにする、より適切でクリーンな方法があります。CompareOptions で正しく動作するクラスSortKeyがあり、それを使用して取得できます

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

.NET ソース コードと実装へのリンクは次のとおりです。

更新 #3:
.NET Framework 4.7.1+ を使用している場合は、この最近の回答で提案されている新しいGlobalizationExtensions クラスを使用する必要があります。

于 2014-05-29T16:34:23.797 に答える
8

CollationInfoクラスを見てみましょう。と呼ばれるアセンブリにありますが、 Microsoft.SqlServer.Management.SqlParser.dllこれをどこで入手できるかは完全にはわかりません。(名前)の静的リストとCollations静的メソッドGetCollationInfo(名前による) があります。

それぞれCollationInfoComparer. とまったく同じではありませんが、StringComparer同様の機能があります。

編集: Microsoft.SqlServer.Management.SqlParser.dll は、共有管理オブジェクト (SMO) パッケージの一部です。SQL Server 2008 R2 用のこの機能は、次の場所からダウンロードできます。

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

編集: CollationInfoという名前のプロパティEqualityComparerがありIEqualityComparer<string>ます。

于 2012-02-21T21:04:00.873 に答える
2

SQL Server のServer.GetStringComparerが役立つ場合があります。

于 2012-02-21T21:08:26.073 に答える