C# 顺序引导生成器
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/1752004/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Sequential Guid Generator
提问by Chris Marisic
Is there any way to get the functionality of the Sql Server 2005+ Sequential Guid generator without inserting records to read it back on round trip or invoking a native win dll call? I saw someone answer with a way of using rpcrt4.dll but I'm not sure if that would be able to work from my hosted environment for production.
有没有办法获得 Sql Server 2005+ Sequential Guid 生成器的功能,而无需插入记录以在往返过程中读回它或调用本机 win dll 调用?我看到有人回答了使用 rpcrt4.dll 的方法,但我不确定这是否能够在我的托管环境中用于生产。
Edit:Working with @John Boker's answer I attempted to turn it into more of a GuidComb generator instead of being dependent on the last generated Guid other than starting over. That for the seed instead of starting with Guid.Empty that I use
编辑:与@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);
}
I based that off the comments on
我基于评论
// 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};
Does this look like the way I'd want to seed a guid with the DateTime or does it look like I should do it in reverse and work backwards from the end of the SqlOrderMap indexes? I'm not too concerned about their being a paging break anytime an initial guid would be created since it would only occur during application recycles.
这看起来像我想用 DateTime 播种一个 guid 的方式,还是我应该反向执行并从 SqlOrderMap 索引的末尾向后工作?我不太担心它们会在任何时候创建初始 guid 时出现分页中断,因为它只会在应用程序回收期间发生。
采纳答案by John Boker
this person came up with something to make sequential guids, here's a link
这个人想出了一些东西来制作连续的 guid,这是一个链接
http://developmenttips.blogspot.com/2008/03/generate-sequential-guids-for-sql.html
http://developmenttips.blogspot.com/2008/03/generate-sequential-guids-for-sql.html
relevant code:
相关代码:
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;
}
}
}
回答by Mike Chaliy
As far I know NHibernate have special generator, called GuidCombGenerator. You can look on it.
据我所知 NHibernate 有一个特殊的生成器,叫做 GuidCombGenerator。你可以看看。
回答by Ian Boyd
You could just use the same Win32 API function that SQL Server uses:
您可以使用SQL Server使用的相同Win32 API 函数:
UuidCreateSequential
and apply some bit-shifting to put the values into big-endian order.
并应用一些位移将值放入大端顺序。
And since you want it in C#:
因为你想在 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);
}
See also
也可以看看
Microsoft's UuidCreateSequential
is just an implementation of a type 1uuid from RFC 4122
.
微软的UuidCreateSequential
只是一个类型 1uuid的实现RFC 4122
。
A uuid has three important parts:
一个 uuid 包含三个重要部分:
node
: (6 bytes)- the computer's MAC addresstimestamp
: (7 bytes)- number of 100 ns intervals since 00:00:00.00, 15 October 1582 (the date of Gregorian reform to the Christian calendar)clockSequenceNumber
(2 bytes) - counter in case you generate a guid faster than 100ns, or you change your mac address
node
: (6 bytes)- 计算机的 MAC 地址timestamp
: (7 bytes)- 自 1582 年 10 月 15 日 00:00:00.00(公历改革到基督教日历的日期)以来的 100 ns 间隔数clockSequenceNumber
(2 个字节)- 计数器,以防您生成的 guid 快于 100ns,或者您更改了 mac 地址
The basic algorithm is:
基本算法是:
- obtain a system-wide lock
- read the last
node
,timestamp
andclockSequenceNumber
from persistent storage (registry/file) - get the current
node
(i.e. MAC address) - get the current
timestamp
- a) if the saved state was not available or corrupted, or the mac address has changed, generate a random
clockSequenceNumber
- b) if the state was available, but the current
timestamp
is the same or older than the saved timestamp, increment theclockSequenceNumber
- a) if the saved state was not available or corrupted, or the mac address has changed, generate a random
- save
node
,timestamp
andclockSequenceNumber
back to persistent storage - release the global lock
- format the guid structure according to the rfc
- 获得系统范围的锁
- 读取最后一个
node
,timestamp
并clockSequenceNumber
从持久存储(注册表/文件) - 获取当前
node
(即 MAC 地址) - 获取当前
timestamp
- a) 如果保存的状态不可用或已损坏,或 mac 地址已更改,则生成一个随机
clockSequenceNumber
- b) 如果状态可用,但当前
timestamp
与保存的时间戳相同或更早,则增加clockSequenceNumber
- a) 如果保存的状态不可用或已损坏,或 mac 地址已更改,则生成一个随机
- 保存
node
,timestamp
并clockSequenceNumber
返回到持久存储 - 释放全局锁
- 根据 rfc 格式化 guid 结构
There is a 4-bit version number, and 2 bit variantthat also need to be ANDed into the data:
有一个 4 位版本号和 2 位变体,它们也需要与数据进行 AND 运算:
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]);
Note: Completely untested; i just eyeballed it from the RFC.
- the byte order might have to be changed (Here is byte order for sql server)
- you might want to create your own version, e.g. Version 6 (version 1-5 are defined). That way you're guaranteed to be universally unique
注意:完全未经测试;我只是从 RFC 中看到它。
- 可能必须更改字节顺序(这是 sql server 的字节顺序)
- 您可能想要创建自己的版本,例如版本 6(定义了版本 1-5)。这样你就可以保证是独一无二的
回答by Ronny Heuschkel
My solution (in VB but easy to convert). It changes the most significant (for SQL Server sorting) first 8 bytes of the GUID to DateTime.UtcNow.Ticks and also has extra code to help the issue of getting the same Ticks multiple times if you call for a new GUID faster than the system clock updates.
我的解决方案(在 VB 中但易于转换)。它将 GUID 的最重要(用于 SQL Server 排序)的前 8 个字节更改为 DateTime.UtcNow.Ticks,并且如果您比系统更快地调用新的 GUID,它还有额外的代码来帮助解决多次获取相同 Ticks 的问题时钟更新。
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
回答by Charles
C# 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;
}
}
}
}
回答by Alex Siepman
回答by Moslem Ben Dhaou
Hereis how NHibernate implements the Guid.Comb algorithm:
下面是 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);
}
回答by Schwarzie2478
Maybe interesting to compare with the other suggestions:
与其他建议进行比较可能很有趣:
EntityFramework Core also implements a sequentialGuidValueGenerator. They generate randoms guids for each value and only change the most significant bytes based on a timestamp and thread-safe increments for sorting in SQL Server.
EntityFramework Core 还实现了一个序列GuidValueGenerator。它们为每个值生成随机 guid,并且仅根据时间戳和线程安全增量更改最重要的字节,以便在 SQL Server 中进行排序。
This leads to values that are all very different but with a timestamp sortable.
这导致所有非常不同但时间戳可排序的值。
回答by Chris Marisic
Not specifically guid but I now normally use a Snowflake style sequential id generator. The same benefits of a guid while having even better clustered index compatibility than a sequential guid.
不是特别的 guid,但我现在通常使用雪花样式的顺序 ID 生成器。具有与 guid 相同的好处,同时具有比顺序 guid 更好的聚集索引兼容性。
回答by toddmo
I just took the NHibernate based answerby Moslem Ben Dhaouand made it an extension function:
我刚刚采用了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);
}
}
}