Generador Guid Secuencial

¿Hay alguna manera de obtener la funcionalidad del generador Sql Server 2005+ Sequential Guid sin insertar registros para volver a leerlo en un viaje de ida y vuelta o invocar una llamada nativa win dll? Vi a alguien responder con una forma de usar rpcrt4.dll pero no estoy seguro de si eso podría funcionar desde mi entorno alojado para producción.

Editar: Trabajando con la respuesta de John Boker, intenté convertirlo en un generador de GuidComb en lugar de depender del último Guid generado, aparte de comenzar de nuevo. Eso para la semilla en lugar de comenzar con Guid.Empty que uso

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

Basé eso en los comentarios sobre

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

¿Esto se ve como desearía sembrar un guid con DateTime o parece que debería hacerlo a la inversa y trabajar hacia atrás desde el final de los índices SqlOrderMap? No estoy demasiado preocupado por ser un salto de paginación en cualquier momento que se crearía un guid inicial, ya que solo ocurriría durante el reciclaje de la aplicación.

a esta persona se le ocurrió algo para hacer guiones secuenciales, aquí hay un enlace

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

código relevante:

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

Simplemente podría usar la misma función API de Win32 que usa SQL Server :

 UuidCreateSequential 

y aplicar algunos cambios de bit para poner los valores en orden big-endian.

Y como lo quieres 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); } 

Ver también

  • ¿Hay un .NET igual a SQL Servers newsequentialid ()

UuidCreateSequential de Microsoft es solo una implementación de un UuidCreateSequential tipo 1 de RFC 4122 .

Un uuid tiene tres partes importantes:

  • node : (6 bytes) : la dirección MAC de la computadora
  • timestamp : (7 bytes) – número de intervalos de 100 ns desde 00: 00: 00.00, 15 de octubre de 1582 (fecha de la reforma gregoriana para el calendario cristiano)
  • clockSequenceNumber (2 bytes) – contador en caso de que genere un guid más rápido que 100ns, o cambie su dirección MAC

El algoritmo básico es:

  1. obtener un locking de todo el sistema
  2. lea el último node , timestamp y clockSequenceNumber del almacenamiento persistente (registro / archivo)
  3. obtener el node actual (es decir, la dirección MAC)
  4. obtener la timestamp actual
    • a) si el estado guardado no estaba disponible o dañado, o la dirección MAC ha cambiado, genere un clockSequenceNumber azar
    • b) si el estado estaba disponible, pero la timestamp actual es la misma o anterior a la marca de tiempo guardada, incremente el clockSequenceNumber
  5. save node , timestamp y clockSequenceNumber nuevo a almacenamiento persistente
  6. liberar el locking global
  7. formatear la estructura guid de acuerdo con el rfc

Hay un número de versión de 4 bits y una variante de 2 bits que también debe ser ANDed en los datos:

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

Nota : completamente no probado; Acabo de verlo desde el RFC.

  • el orden de bytes puede tener que ser cambiado ( Aquí está el orden de bytes para el servidor SQL )
  • Es posible que desee crear su propia versión, por ejemplo, la Versión 6 (la versión 1-5 está definida). De esta forma, tienes la garantía de ser universalmente único

Así es como NHibernate implementa el algoritmo 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); } 

Un guiado secuencial que se actualiza frecuentemente (al menos 3 veces por milisegundo) se puede encontrar aquí . Se crea con código C # normal (sin código nativo).

Versión de 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; } } 

}

Mi solución (en VB pero fácil de convertir). Cambia los primeros 8 bytes del GUID a DateTime.UtcNow.Ticks, el más significativo (para la clasificación de SQL Server) y también tiene un código adicional para ayudar a resolver el problema de obtener los mismos Ticks varias veces si solicita un nuevo GUID más rápido que el sistema actualizaciones de reloj.

 Private ReadOnly _toSeqGuidLock As New Object() '''  ''' Replaces the most significant eight bytes of the GUID (according to SQL Server ordering) with the current UTC-timestamp. '''  ''' Thread-Safe  _ 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 

Hasta donde sé, NHibernate tiene un generador especial, llamado GuidCombGenerator. Puedes mirarlo.

Tal vez sea interesante compararlo con las otras sugerencias:

EntityFramework Core también implementa un GuidValueGenerator secuencial. Generan guias de randoms para cada valor y solo cambian los bytes más significativos en función de una marca de tiempo y de incrementos de subprocesos seguros para clasificar en SQL Server.

enlace fuente

Esto lleva a valores que son todos muy diferentes pero con una marca de tiempo ordenable.

Acabo de tomar la respuesta basada en NHibernate por el musulmán Ben Dhaou y la convertí en una función de extensión:

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

Acabo de ver esta pregunta … Resulta que soy el autor de una pequeña biblioteca .NET de código abierto para generar GUID estilo COMB.

La biblioteca es compatible con el método original (compatible con el tipo de datetime y datetime SQL Server) y con las marcas de tiempo de Unix, que tienen más precisión de tiempo. También incluye una variante que funciona mejor para PostgrSQL:

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

No específicamente guid, pero ahora normalmente uso un generador de id secuencial estilo Snowflake. Los mismos beneficios de un GUID mientras que tiene una compatibilidad con el índice en clúster aún mejor que un GUID secuencial.

Flakey para .NET Core

IdGen para .NET Framework