Последовательный генератор Guid - PullRequest
41 голосов
/ 18 ноября 2009

Есть ли способ получить функциональность генератора Sql Server 2005+ Sequential Guid, не вставляя записи для его повторного считывания в оба конца или не вызывая собственный вызов win dll? Я видел, как кто-то ответил с помощью способа использования rpcrt4.dll, но я не уверен, сможет ли это работать из моей размещенной среды для производства.

Редактировать: Работая с ответом @John Boker, я попытался превратить его в генератор GuidComb, вместо того чтобы зависеть от последнего сгенерированного Guid, кроме как начать заново. Что за семя вместо того, чтобы начинать с Guid.Empty, который я использую

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

Я основал это на комментариях к

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

Похоже ли это на то, как я хотел бы заполнить guid с DateTime, или похоже, что я должен сделать это в обратном порядке и работать в обратном направлении от конца индексов SqlOrderMap? Меня не слишком беспокоит то, что они будут прерываться на пейджинг всякий раз, когда будет создаваться начальный guid, поскольку он будет происходить только во время перезапусков приложений.

Ответы [ 11 ]

67 голосов
/ 02 марта 2012

Вы можете просто использовать ту же функцию Win32 API, которую использует SQL Server :

UuidCreateSequential

и применить сдвиг битов, чтобы привести значения в порядок с прямым порядком байтов.

А так как вы хотите это в 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);
}

Смотри также


Microsoft UuidCreateSequential является просто реализацией типа 1 uuid из RFC 4122.

У uuid есть три важные части:

  • node: (6 байт) - MAC-адрес компьютера
  • timestamp: (7 байт) - число интервалов по 100 нс с 00:00 до 00:00 15 октября 1582 г. (дата григорианской реформы по христианскому календарю)
  • clockSequenceNumber (2 байта) - счетчик, если вы генерируете гид быстрее, чем 100 нс, или вы меняете свой MAC-адрес

Основной алгоритм:

  1. получить общесистемную блокировку
  2. чтение последних node, timestamp и clockSequenceNumber из постоянного хранилища (реестр / файл)
  3. получить текущий node (т.е. MAC-адрес)
  4. получить текущий timestamp
    • a) если сохраненное состояние не было доступно или повреждено, или изменился mac-адрес, генерировать случайное значение clockSequenceNumber
    • b) если состояние было доступно, но текущий timestamp такой же или старше сохраненной временной отметки, увеличьте clockSequenceNumber
  5. сохранить node, timestamp и clockSequenceNumber обратно в постоянное хранилище
  6. снять глобальную блокировку
  7. форматирование структуры guid в соответствии с rfc

Существует 4-битный номер версии и 2-битный вариант , которые также необходимо вставить в данные:

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

Примечание : полностью не проверено; я просто посмотрел это из RFC.

  • порядок байтов может быть изменен ( Вот порядок байтов для сервера sql )
  • вы можете захотеть создать свою собственную версию, например Версия 6 (определены версии 1-5). Таким образом, вы гарантированно станете уникальным
22 голосов
/ 18 ноября 2009

этот человек придумал что-то, чтобы сделать последовательные руководства, вот ссылка

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

соответствующий код:

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;
        }
    }
}
17 голосов
/ 24 августа 2014

Здесь - это то, как NHibernate реализует алгоритм 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);
}
7 голосов
/ 02 ноября 2013

Последовательное руководство, которое часто обновляется (не менее 3 раз в миллисекунду), можно найти здесь . Он создается с обычным кодом C # (без вызова нативного кода).

4 голосов
/ 03 октября 2016

Может быть интересно сравнить с другими предложениями:

В EntityFramework Core также реализован последовательныйGuidValueGenerator. Они генерируют случайные направляющие для каждого значения и изменяют только самые значимые байты на основе временной метки и поточно-ориентированных приращений для сортировки в SQL Server.

ссылка на источник

Это приводит к значениям, которые сильно отличаются друг от друга, но с возможностью сортировки по отметке времени.

4 голосов
/ 06 июня 2013

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;

    }

}

}

3 голосов
/ 25 февраля 2017

Я просто взял ответ на основе NHibernate от Мусульманин Бен Дхау и сделал его функцией расширения:

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 голосов
/ 26 февраля 2013

Мое решение (в VB, но легко конвертировать). Он изменяет наиболее значимые (для сортировки SQL Server) первые 8 байтов GUID на DateTime.UtcNow.Ticks, а также содержит дополнительный код, помогающий решить проблему получения одинаковых тиков несколько раз, если вы вызываете новый GUID быстрее, чем система обновления часов.

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
2 голосов
/ 05 июля 2017

Я только что увидел этот вопрос ... Я оказался автором небольшой библиотеки .NET с открытым исходным кодом для генерации GUID в стиле COMB.

Библиотека поддерживает как оригинальный метод (совместимый с типом datetime SQL Server), так и метод, использующий метки времени Unix, которые имеют большую точность времени. Он также включает вариант, который лучше работает для PostgrSQL:

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

2 голосов
/ 03 октября 2016

Не совсем guid, но теперь я обычно использую генератор последовательных идентификаторов в стиле Снежинки. Те же преимущества guid, в то время как совместимость с кластерными индексами даже лучше, чем у последовательного guid.

Flakey для .NET Core

IdGen для .NET Framework

...