Генератор последовательностей
Есть ли способ получить функциональность генератора Sql Server 2005+ Sequential Guid без вставки записей, чтобы прочитать его обратно в оба конца или вызвать вызов win dll win? Я видел, как кто-то ответил с использованием 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};
Означает ли это, что я хочу, чтобы семена гида с помощью DateTime, или похоже, что я должен делать это в обратном порядке и работать назад с конца индексов SqlOrderMap? Я не слишком беспокоюсь о том, что они будут перерывом поискового вызова в любое время, когда будет создан начальный guid, поскольку он будет возникать только во время повторного использования приложения.
Ответы
Ответ 1
этот человек придумал что-то, чтобы сделать последовательные контуры, здесь ссылка
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;
}
}
}
Ответ 2
Вы можете просто использовать ту же функцию Win32 API, которую использует SQL Server:
UuidCreateSequential
и примените некоторое смещение бит, чтобы поместить значения в ординарный порядок.
И так как вы хотите его в С#:
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
.
Уид имеет три важные части:
-
node
: (6 байт) - MAC-адрес компьютера
-
timestamp
: (7 байт) - число интервалов в 100 нс с 00: 00: 00.00, 15 октября 1582 (дата репрессий в григорианском стиле в христианском календаре).
-
clockSequenceNumber
(2 байта) - счетчик в случае, если вы создаете guid быстрее, чем 100ns, или вы меняете свой MAC-адрес
Основной алгоритм:
- получить системную блокировку
- прочитайте последние
node
, timestamp
и clockSequenceNumber
из постоянного хранилища (реестр/файл)
- получить текущий
node
(т.е. MAC-адрес)
- получить текущий
timestamp
-
- a) если сохраненное состояние недоступно или повреждено, или изменился адрес mac, генерируйте случайный
clockSequenceNumber
- b) если состояние доступно, но текущий
timestamp
является тем же или старше сохраненной метки времени, увеличьте clockSequenceNumber
- сохранить
node
, timestamp
и clockSequenceNumber
обратно в постоянное хранилище
- освободить глобальную блокировку
- форматируйте структуру направляющих в соответствии с 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 определена). Таким образом, вы гарантированно будете универсально уникальными.
Ответ 3
Здесь описано, как 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);
}
Ответ 4
Последовательный указатель, который часто обновляется (не менее 3 раз за миллисекунду), можно найти здесь. Это создание с регулярным кодом С# (без вызова внутреннего кода).
Ответ 5
Мое решение (в VB, но легко конвертировать). Он изменяет наиболее значимые (для сортировки SQL Server) первые 8 байтов идентификатора GUID на DateTime.UtcNow.Ticks, а также имеет дополнительный код, помогающий решить проблему получения одинаковых 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
Ответ 6
Версия С#
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;
}
}
}
Ответ 7
Насколько я знаю, NHibernate имеет специальный генератор, называемый GuidCombGenerator. Вы можете посмотреть на него.
Ответ 8
Возможно, интересно сравнить с другими предложениями:
Ядро EntityFramework также реализует последовательныйGuidValueGenerator.
Они генерируют подсказки randoms для каждого значения и изменяют только самые важные байты на основе временной метки и потокобезопасных приращений для сортировки в SQL Server.
ссылка источника
Это приводит к значениям, которые все очень разные, но с сортировкой по времени.
Ответ 9
Я только что увидел этот вопрос... Я, случается, являюсь автором небольшой библиотеки .NET с открытым исходным кодом для генерации GUID в стиле COMB.
Библиотека поддерживает как исходный метод (совместимый с типом SQL Server datetime
), так и один с использованием временных меток Unix, которые имеют больше времени. Он также включает вариант, который лучше подходит для PostgrSQL:
https://github.com/richardtallent/RT.Comb
Ответ 10
Не специально, но теперь я обычно использую генератор последовательных идентификаторов стиля Snowflake. Те же преимущества руководства, хотя и имеют лучшую кластерную совместимость индексов, чем последовательный указатель.
Flakey для .NET Core
IdGen для .NET Framework
Ответ 11
Я только что принял ответ на 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);
}
}
}