顺序Guid生成器

有没有办法获得Sql Server 2005+ Sequential Guid生成器的function,而无需插入logging来往返读取或调用本机的win dll调用? 我看到有人用rpcrt4.dll的方式回答,但我不确定是否可以从我的托pipe环境进行生产。

编辑:使用@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}; 

这看起来像我想要种子与date时间guid的方式,或者它看起来像我应该做倒过来,并从SqlOrderMap索引的末尾工作? 我不关心他们作为分页符中断,因为只有在应用程序回收期间才会创build初始的guid。

这个人想出了一些连续的指导,这里有一个链接

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

您可以使用SQL Server使用的相同的Win32 API函数 :

 UuidCreateSequential 

而且,既然你想在C#中使用它:

 private class NativeMethods { [DllImport("rpcrt4.dll", SetLastError=true)] public static extern int UuidCreateSequential(out Guid guid); } public static Guid CreateGuid() { const int RPC_S_OK = 0; Guid guid; int result = NativeMethods.UuidCreateSequential(out guid); if (result == RPC_S_OK) return guid; else return Guid.NewGuid(); } 

也可以看看

  • 是否有一个.NET Equalent SQL服务器newsequentialid()

微软的UuidCreateSequential只是RFC 4122的一个types为 uuid的实现。

一个uuid有三个重要部分:

  • node(6字节) – 计算机的MAC地址
  • timestamp(7个字节) – 自1582年10月15日00:00:00.00(公历改革date)
  • clockSequenceNumber (2字节) – 计数器,如果您生成的GUID超过100ns,或者您更改您的MAC地址

基本的algorithm是:

  1. 获取系统范围的locking
  2. 从持久性存储(registry/文件)读取最后一个nodetimestampclockSequenceNumber
  3. 获取当前node (即MAC地址)
  4. 获取当前的timestamp
    • a)如果保存的状态不可用或损坏,或者mac地址已经改变,则生成一个随机的clockSequenceNumber
    • b)如果状态可用,但当前timestamp与保存的时间戳相同或更旧,则将clockSequenceNumber
  5. nodetimestampclockSequenceNumber保存到持久性存储
  6. 释放全局locking
  7. 根据rfc格式化guid结构

有一个4位版本号和2位variables ,也需要与数据进行“与”运算:

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

:完全未经testing; 我只是从RFC中弄明白了。

  • 字节顺序可能不得不改变( 这里是sql服务器的字节顺序 )
  • 您可能需要创build自己的版本,例如版本6(已定义版本1-5)。 这样你就可以保证是独一无二的

下面是NHibernate如何实现Guid.Combalgorithm:

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

可以在这里find一个经常更新(每毫秒至less3次)的序列guid。 它是用常规的C#代码(没有本地代码调用)创build的。

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

}

我的解决scheme(在VB中,但很容易转换)。 它将最重要的(对于SQL Serversorting)前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 

据我所知,NHibernate有特殊的生成器,称为GuidCombGenerator。 你可以看看它。

也许有趣的是与其他build议进行比较:

EntityFramework Core还实现了一个sequentialGuidValueGenerator。 它们为每个值生成随机数指针,并且只根据时间戳和线程安全增量更改SQL Server中sorting的最重要字节。

源链接

这导致值都是非常不同的,但有时间戳sorting。

不是特别的指导,但我现在通常使用雪花风格顺序id生成器。 guid具有相同的好处,同时具有更好的聚簇索引兼容性。

.NET核心的Flakey

用于.NET Framework的IdGen

我只是采用了 Moslem Ben Dhaou的 基于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); } } } 

我刚刚看到这个问题…我碰巧是一个用于生成COMB风格的GUID的小型开源.NET库的作者。

该库既支持原始方法(兼容SQL Server的datetimetypes),也支持使用Unix时间戳的方法,这些时间戳具有更高的时间精度。 它还包括一个更适合PostgrSQL的变体:

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