Domanda

Io sono l'implementazione di un livello di cache tra il mio database e il mio codice C#.L'idea è quella di memorizzare nella cache i risultati di alcune query al DB in base ai parametri per la query.Il database utilizza le regole di confronto predefinite - o SQL_Latin1_General_CP1_CI_AS o Latin1_General_CI_AS, credo che sulla base di alcune brevi ricerche su google sono equivalenti per la parità, solo diverso per l'ordinamento.

Ho bisogno di un .NET StringComparer che mi può dare lo stesso problema, almeno per la parità di test e hashcode generazione, come le regole di confronto del database che si sta utilizzando.L'obiettivo è quello di essere in grado di utilizzare il StringComparer in un .NET dizionario di codice C# per determinare se una particolare stringa chiave è già in cache o non.

Davvero un esempio semplificato:

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

La ragione per cui è importante che il StringComparer partite di regole di confronto del database è che sia i falsi positivi e i falsi negativi avrebbe effetti negativi per il codice.

Se il StringComparer dice che i due tasti A e B sono uguali quando il database ritiene che essi sono distinti, quindi ci potrebbero essere due righe nel database con le due chiavi, ma la cache impedirà il secondo di aver mai restituito, se richiesto, a e B in successione - perché il ottenere per B erroneamente colpito la cache e restituire l'oggetto è stato recuperato per la A.

Il problema è più sottile, se la StringComparer dice che A e B sono diversi, quando il database crede che siano uguali, ma non per questo meno problematica.GetObject chiamate entrambe le chiavi andrebbe bene, e di restituire oggetti corrispondenti alla stessa riga del database.Ma poi, chiamando SaveObject con chiave A lasciare la cache non corretto;c'è ancora una voce della cache per il tasto B che ha i vecchi dati.Una successiva GetObject(B) dare informazioni obsolete.

Così per il mio codice funziona correttamente ho bisogno di una StringComparer per abbinare il comportamento del database per la parità di test e hashcode generazione.Il mio usare google finora ha dato un sacco di informazioni circa il fatto che le regole di confronto di SQL e .NET confronti non sono esattamente equivalenti, ma nessun dettaglio su quali siano le differenze, se si sono limitati a solo differenze di ordinamento, o se è possibile trovare un StringComparer che è equivalente a un specifiche Regole di confronto di SQL se una soluzione generica non è necessario.

(Nota a margine - il livello di caching è general purpose, quindi non posso fare particolare ipotesi circa la natura della chiave è e ciò che confronto sarebbe opportuno.Tutte le tabelle del database condividono lo stesso server predefinito di regole di confronto.Ho solo bisogno di abbinare il confronto come esiste)

È stato utile?

Soluzione

Dai un'occhiata al CollationInfo classe. Si trova in un assembly chiamato Microsoft.SqlServer.Management.SqlParser.dll Anche se non sono del tutto sicuro di dove trovarlo. C'è un elenco statico di Collations (nomi) e un metodo statico GetCollationInfo (per nome).

A testa CollationInfo ha un Comparer. Non è esattamente lo stesso di un StringComparer ma ha funzionalità simili.

MODIFICARE: Microsoft.sqlserver.management.sqlparser.dll fa parte del pacchetto SMO di Management Objects (SMO). Questa funzione può essere scaricata per SQL Server 2008 R2 qui:

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

MODIFICARE: CollationInfo ha una proprietà denominata EqualityComparer che è un IEqualityComparer<string>.

Altri suggerimenti

Recentemente ho dovuto affrontare lo stesso problema:Ho bisogno di un IEqualityComparer<string> che si comporta in SQL-come stile.Ho provato CollationInfo e la sua EqualityComparer.Se il tuo DB è sempre _AS (accentati) quindi la tua soluzione funziona, ma nel caso, se modificare le regole di confronto AI o WI o qualunque cosa "insensibile" altro hash, si rompe.
Perché?Se si decompilare Microsoft.SqlServer.Management.SqlParser.dll e guardare all'interno troverete che CollationInfo utilizza internamente CultureAwareComparer.GetHashCode (è classe interna di mscorlib.dll) e, infine, è il seguente:

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

Come si può vedere è in grado di produrre lo stesso hashcode per "aa" e "AA", ma non per "äå" e "aa" (che sono la stessa cosa, se si ignorano i segni diacritici (IA) nella maggior parte delle culture, quindi, dovrebbero avere lo stesso hashcode).Non so perché l' .API di rete è limitata da questo, ma si dovrebbe capire dove il problema può venire da.Per ottenere lo stesso hashcode per le stringhe di caratteri con segni diacritici è possibile effettuare le seguenti operazioni: creare attuazione di IEqualityComparer<T> attuazione del GetHashCode che sarà chiamata appropriata CompareInfo's oggetto GetHashCodeOfString via di riflessione, perché questo metodo è interno e non può essere utilizzato direttamente.Ma chiamando direttamente con il corretto CompareOptions produrrà il risultato desiderato:Vedi questo esempio:

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

L'output è:

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

Lo so che sembra l'hack, ma dopo aver ispezionato decompilato .NET code non sono sicuro se c'è qualche altra opzione nel caso in cui il generico funzionalità è necessario.Quindi, essere sicuri che si sarà non cadere in trappola, questo non è completamente corretto API.
AGGIORNAMENTO:
Ho anche creato il gist con eventuale attuazione di "SQL-come operatore di confronto" utilizzando CollationInfo.Ci deve anche essere prestato abbastanza attenzione dove cercare "stringa di insidie" nel codice di base, quindi se la stringa di confronto, hashcode, l'uguaglianza deve essere cambiato in "regole di confronto di SQL-like", quei luoghi sono al 100% sarà rotto, quindi dovrete trovare e ispezionare tutti i luoghi che può essere rotto.
UPDATE #2:
C'è di meglio e in modo più pulito per fare GetHashCode() il trattamento di CompareOptions.C'è la classe SortKey che funziona correttamente con CompareOptions e può essere recuperato utilizzando

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

Qui è il link per .NET codice sorgente e di attuazione.

SQL Server's Server.getStringComparer può essere di qualche uso.

Quello che segue è molto più semplice:

System.Globalization.CultureInfo.GetCultureInfo(1033)
              .CompareInfo.GetStringComparer(CompareOptions.IgnoreCase | CompareOptions.IgnoreKanaType | CompareOptions.IgnoreWidth)

Viene da https://docs.microsoft.com/en-us/dotnet/api/system.globalization.globalizationExtensions?view=netframework-4.8

Calcola correttamente l'hashcode fornite le opzioni fornite. Dovrai ancora tagliare manualmente gli spazi finali, come scartati da ANSI SQL ma non in .NET

Ecco un wrapper che taglia gli spazi.

using System.Collections.Generic;
using System.Globalization;

namespace Wish.Core
{
    public class SqlStringComparer : IEqualityComparer<string>
    {
        public static IEqualityComparer<string> Instance { get; }

        private static IEqualityComparer<string> _internalComparer =
            CultureInfo.GetCultureInfo(1033)
                       .CompareInfo
                       .GetStringComparer(CompareOptions.IgnoreCase | CompareOptions.IgnoreKanaType | CompareOptions.IgnoreWidth);



        private SqlStringComparer()
        {
        }

        public bool Equals(string x, string y)
        {
            //ANSI sql doesn't consider trailing spaces but .Net does
            return _internalComparer.Equals(x?.TrimEnd(), y?.TrimEnd());
        }

        public int GetHashCode(string obj)
        {
            return _internalComparer.GetHashCode(obj?.TrimEnd());
        }

        static SqlStringComparer()
        {
            Instance = new SqlStringComparer();
        }
    }
}

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top