web-dev-qa-db-fra.com

Générateur de guidage séquentiel

Existe-t-il un moyen d'obtenir les fonctionnalités du générateur Sql Server 2005+ Sequential Guid sans insérer d'enregistrements pour le relire lors d'un aller-retour ou appeler un appel natif de DLL de gain? J'ai vu quelqu'un répondre avec une façon d'utiliser rpcrt4.dll mais je ne sais pas si cela pourrait fonctionner à partir de mon environnement hébergé pour la production.

Edit: Travailler avec la réponse de @John Boker J'ai essayé de le transformer en plus d'un générateur GuidComb au lieu de dépendre du dernier Guid généré autre que de recommencer. Cela pour la graine au lieu de commencer par Guid. Vide que j'utilise

public SequentialGuid()
{
    var tempGuid = Guid.NewGuid();
    var bytes = tempGuid.ToByteArray();
    var time = DateTime.Now;
    bytes[3] = (byte) time.Year;
    bytes[2] = (byte) time.Month;
    bytes[1] = (byte) time.Day;
    bytes[0] = (byte) time.Hour;
    bytes[5] = (byte) time.Minute;
    bytes[4] = (byte) time.Second;
    CurrentGuid = new Guid(bytes);
}

J'ai basé cela sur les commentaires

// 3 - the least significant byte in Guid ByteArray 
        [for SQL Server ORDER BY clause]
// 10 - the most significant byte in Guid ByteArray 
        [for SQL Server ORDERY BY clause]
SqlOrderMap = new[] {3, 2, 1, 0, 5, 4, 7, 6, 9, 8, 15, 14, 13, 12, 11, 10};

Est-ce que cela ressemble à la façon dont je voudrais semer un guid avec le DateTime ou est-ce que je devrais le faire à l'envers et travailler en arrière à partir de la fin des index SqlOrderMap? Je ne suis pas trop préoccupé par le fait qu'il s'agisse d'une pause de pagination à chaque fois qu'un guide initial serait créé car il ne se produirait que lors des recyclages d'application.

43
Chris Marisic

cette personne a trouvé quelque chose pour faire des guides séquentiels, voici un lien

http://developmenttips.blogspot.com/2008/03/generate-sequential-guids-for-sql.html

code pertinent:

public class SequentialGuid {
    Guid _CurrentGuid;
    public Guid CurrentGuid {
        get {
            return _CurrentGuid;
        }
    }

    public SequentialGuid() {
        _CurrentGuid = Guid.NewGuid();
    }

    public SequentialGuid(Guid previousGuid) {
        _CurrentGuid = previousGuid;
    }

    public static SequentialGuid operator++(SequentialGuid sequentialGuid) {
        byte[] bytes = sequentialGuid._CurrentGuid.ToByteArray();
        for (int mapIndex = 0; mapIndex < 16; mapIndex++) {
            int bytesIndex = SqlOrderMap[mapIndex];
            bytes[bytesIndex]++;
            if (bytes[bytesIndex] != 0) {
                break; // No need to increment more significant bytes
            }
        }
        sequentialGuid._CurrentGuid = new Guid(bytes);
        return sequentialGuid;
    }

    private static int[] _SqlOrderMap = null;
    private static int[] SqlOrderMap {
        get {
            if (_SqlOrderMap == null) {
                _SqlOrderMap = new int[16] {
                    3, 2, 1, 0, 5, 4, 7, 6, 9, 8, 15, 14, 13, 12, 11, 10
                };
                // 3 - the least significant byte in Guid ByteArray [for SQL Server ORDER BY clause]
                // 10 - the most significant byte in Guid ByteArray [for SQL Server ORDERY BY clause]
            }
            return _SqlOrderMap;
        }
    }
}
25
John Boker

Vous pouvez simplement utiliser la même fonction API Win32 que SQL Server utilise :

UuidCreateSequential

et appliquer un décalage de bits pour mettre les valeurs dans un ordre big-endian.

Et puisque vous le voulez en C #:

private class NativeMethods
{
   [DllImport("rpcrt4.dll", SetLastError=true)]
   public static extern int UuidCreateSequential(out Guid guid);
}

public static Guid NewSequentialID()
{
   //Code is released into the public domain; no attribution required
   const int RPC_S_OK = 0;

   Guid guid;
   int result = NativeMethods.UuidCreateSequential(out guid);
   if (result != RPC_S_OK)
      return Guid.NewGuid();

   //Endian swap the UInt32, UInt16, and UInt16 into the big-endian order (RFC specified order) that SQL Server expects
   //See https://stackoverflow.com/a/47682820/12597
   //Short version: UuidCreateSequential writes out three numbers in litte, rather than big, endian order
   var s = guid.ToByteArray();
   var t = new byte[16];

   //Endian swap UInt32
   t[3] = s[0];
   t[2] = s[1];
   t[1] = s[2];
   t[0] = s[3];
   //Endian swap UInt16
   t[5] = s[4];
   t[4] = s[5];
   //Endian swap UInt16
   t[7] = s[6];
   t[6] = s[7];
   //The rest are already in the proper order
   t[8] = s[8];
   t[9] = s[9];
   t[10] = s[10];
   t[11] = s[11];
   t[12] = s[12];
   t[13] = s[13];
   t[14] = s[14];
   t[15] = s[15];

   return new Guid(t);
}

Voir également


UuidCreateSequential de Microsoft n'est qu'une implémentation d'un type 1 uuid de RFC 4122 .

Un uuide comprend trois parties importantes:

  • node: (6 octets) - l'adresse MAC de l'ordinateur
  • timestamp: (7 octets) - nombre d'intervalles de 100 ns depuis 00: 00: 00.00, 15 octobre 1582 (date de la réforme grégorienne du calendrier chrétien)
  • clockSequenceNumber (2 octets) - compteur au cas où vous générez un guid plus rapidement que 100ns, ou si vous changez votre adresse mac

L'algorithme de base est:

  1. obtenir une serrure à l'échelle du système
  2. lire les derniers node, timestamp et clockSequenceNumber du stockage persistant (registre/fichier)
  3. obtenir le node actuel (c'est-à-dire l'adresse MAC)
  4. obtenir le timestamp actuel
    • a) si l'état enregistré n'était pas disponible ou corrompu, ou si l'adresse mac a changé, générez un clockSequenceNumber aléatoire
    • b) si l'état était disponible, mais que le timestamp actuel est identique ou plus ancien que l'horodatage enregistré, incrémentez le clockSequenceNumber
  5. enregistrer node, timestamp et clockSequenceNumber dans un stockage persistant
  6. libérer le verrou global
  7. formater la structure guid selon le rfc

Il existe un numéro de version 4 bits et 2 bits variante qui doivent également être ET dans les données:

guid = new Guid(
      timestamp & 0xFFFFFFFF,  //timestamp low
      (timestamp >> 32) & 0xFFFF, //timestamp mid
      ((timestamp >> 40) & 0x0FFF), | (1 << 12) //timestamp high and version (version 1)
      (clockSequenceNumber & 0x3F) | (0x80), //clock sequence number and reserved
      node[0], node[1], node[2], node[3], node[4], node[5], node[6]);

Remarque : Complètement non testé; je viens de le regarder du RFC.

  • l'ordre des octets devra peut-être être modifié ( Voici l'ordre des octets pour le serveur SQL )
  • vous voudrez peut-être créer votre propre version, par exemple Version 6 (les versions 1-5 sont définies). De cette façon, vous êtes garanti d'être universellement unique
68
Ian Boyd

Ici est la façon dont NHibernate implémente l'algorithme Guid.Comb:

private Guid GenerateComb()
{
    byte[] guidArray = Guid.NewGuid().ToByteArray();

    DateTime baseDate = new DateTime(1900, 1, 1);
    DateTime now = DateTime.Now;

    // Get the days and milliseconds which will be used to build the byte string 
    TimeSpan days = new TimeSpan(now.Ticks - baseDate.Ticks);
    TimeSpan msecs = now.TimeOfDay;

    // Convert to a byte array 
    // Note that SQL Server is accurate to 1/300th of a millisecond so we divide by 3.333333 
    byte[] daysArray = BitConverter.GetBytes(days.Days);
    byte[] msecsArray = BitConverter.GetBytes((long) (msecs.TotalMilliseconds / 3.333333));

    // Reverse the bytes to match SQL Servers ordering 
    Array.Reverse(daysArray);
    Array.Reverse(msecsArray);

    // Copy the bytes into the guid 
    Array.Copy(daysArray, daysArray.Length - 2, guidArray, guidArray.Length - 6, 2);
    Array.Copy(msecsArray, msecsArray.Length - 4, guidArray, guidArray.Length - 4, 4);

    return new Guid(guidArray);
}
17
Moslem Ben Dhaou

Un guide séquentiel qui se met à jour souvent (au moins 3 fois par milliseconde), peut être trouvé ici . Il est créé avec du code C # normal (pas d'appel de code natif).

7
Alex Siepman

Version C #

    public static Guid ToSeqGuid()
    {
        Int64 lastTicks = -1;
        long ticks = System.DateTime.UtcNow.Ticks;

        if (ticks <= lastTicks)
        {
            ticks = lastTicks + 1;
        }

        lastTicks = ticks;

        byte[] ticksBytes = BitConverter.GetBytes(ticks);

        Array.Reverse(ticksBytes);

        Guid myGuid = new Guid();
        byte[] guidBytes = myGuid.ToByteArray();

        Array.Copy(ticksBytes, 0, guidBytes, 10, 6);
        Array.Copy(ticksBytes, 6, guidBytes, 8, 2);

        Guid newGuid = new Guid(guidBytes);

        string filepath = @"C:\temp\TheNewGuids.txt";
        using (StreamWriter writer = new StreamWriter(filepath, true))
        {
            writer.WriteLine("GUID Created =  " + newGuid.ToString());
        }

        return newGuid;

    }

}

}

4
Charles

Peut-être intéressant de comparer avec les autres suggestions:

EntityFramework Core implémente également un séquentielGuidValueGenerator. Ils génèrent des guides aléatoires pour chaque valeur et ne modifient que les octets les plus significatifs en fonction d'un horodatage et d'incréments thread-safe pour le tri dans SQL Server.

lien source

Cela conduit à des valeurs qui sont toutes très différentes mais avec un horodatage triable.

4
Schwarzie2478

Ma solution (en VB mais facile à convertir). Elle modifie les 8 octets les plus significatifs (pour le tri SQL Server) des GUID vers DateTime.UtcNow .Ticks et a également du code supplémentaire pour résoudre le problème d'obtenir plusieurs fois les mêmes ticks si vous appelez pour un nouveau GUID plus rapide que les mises à jour de l'horloge système.

Private ReadOnly _toSeqGuidLock As New Object()
''' <summary>
''' Replaces the most significant eight bytes of the GUID (according to SQL Server ordering) with the current UTC-timestamp.
''' </summary>
''' <remarks>Thread-Safe</remarks>
<System.Runtime.CompilerServices.Extension()> _
Public Function ToSeqGuid(ByVal guid As Guid) As Guid

    Static lastTicks As Int64 = -1

    Dim ticks = DateTime.UtcNow.Ticks

    SyncLock _toSeqGuidLock

        If ticks <= lastTicks Then
            ticks = lastTicks + 1
        End If

        lastTicks = ticks

    End SyncLock

    Dim ticksBytes = BitConverter.GetBytes(ticks)

    Array.Reverse(ticksBytes)

    Dim guidBytes = guid.ToByteArray()

    Array.Copy(ticksBytes, 0, guidBytes, 10, 6)
    Array.Copy(ticksBytes, 6, guidBytes, 8, 2)

    Return New Guid(guidBytes)

End Function
3
Ronny Heuschkel

Je viens de prendre la réponse basée sur NHibernate par Moslem Ben Dhao et en ai fait une fonction d'extension:

using System;

namespace Atlas.Core.Kernel.Extensions
{
  public static class Guids
  {
    public static Guid Comb(this Guid source)
    {
      byte[] guidArray = source.ToByteArray();

      DateTime baseDate = new DateTime(1900, 1, 1);
      DateTime now = DateTime.Now;

      // Get the days and milliseconds which will be used to build the byte string 
      TimeSpan days = new TimeSpan(now.Ticks - baseDate.Ticks);
      TimeSpan msecs = now.TimeOfDay;

      // Convert to a byte array 
      // Note that SQL Server is accurate to 1/300th of a millisecond so we divide by 3.333333 
      byte[] daysArray = BitConverter.GetBytes(days.Days);
      byte[] msecsArray = BitConverter.GetBytes((long)(msecs.TotalMilliseconds / 3.333333));

      // Reverse the bytes to match SQL Servers ordering 
      Array.Reverse(daysArray);
      Array.Reverse(msecsArray);

      // Copy the bytes into the guid 
      Array.Copy(daysArray, daysArray.Length - 2, guidArray, guidArray.Length - 6, 2);
      Array.Copy(msecsArray, msecsArray.Length - 4, guidArray, guidArray.Length - 4, 4);

      return new Guid(guidArray);
    }
  }
}
3
toddmo

Pour autant que je sache, NHibernate possède un générateur spécial, appelé GuidCombGenerator. Vous pouvez le regarder.

2
Mike Chaliy

Je viens de voir cette question ... Il se trouve que je suis l'auteur d'une petite bibliothèque .NET open source pour générer des GUID de style COMB.

La bibliothèque prend en charge à la fois la méthode d'origine (compatible avec le type datetime de SQL Server) et une utilisant des horodatages Unix, qui ont plus de précision temporelle. Il comprend également une variante qui fonctionne mieux pour PostgrSQL:

https://github.com/richardtallent/RT.Comb

2
richardtallent

Pas spécifiquement guid mais j'utilise maintenant normalement un générateur d'ID séquentiel de style Snowflake. Les mêmes avantages d'un guid tout en ayant une meilleure compatibilité d'index clusterisé qu'un guid séquentiel.

Flakey pour .NET Core

IdGen pour .NET Framework

2
Chris Marisic