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

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-06 20:29:56  来源:igfitidea点击:

Sequential Guid Generator

c#guidsequence

提问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 UuidCreateSequentialis 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 address
  • timestamp: (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:

基本算法是:

  1. obtain a system-wide lock
  2. read the last node, timestampand clockSequenceNumberfrom persistent storage (registry/file)
  3. get the current node(i.e. MAC address)
  4. 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 timestampis the same or older than the saved timestamp, increment the clockSequenceNumber
  5. save node, timestampand clockSequenceNumberback to persistent storage
  6. release the global lock
  7. format the guid structure according to the rfc
  1. 获得系统范围的锁
  2. 读取最后一个nodetimestampclockSequenceNumber从持久存储(注册表/文件)
  3. 获取当前node(即 MAC 地址)
  4. 获取当前 timestamp
    • a) 如果保存的状态不可用或已损坏,或 mac 地址已更改,则生成一个随机 clockSequenceNumber
    • b) 如果状态可用,但当前timestamp与保存的时间戳相同或更早,则增加clockSequenceNumber
  5. 保存nodetimestampclockSequenceNumber返回到持久存储
  6. 释放全局锁
  7. 根据 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 中看到它。

回答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

A Sequential guid that updates often (at least 3 times per milisecond), can be found here. It is create with regular C# code (no native code call).

可以在此处找到经常更新(每毫秒至少 3 次)的 Sequential guid 。它是使用常规 C# 代码创建的(没有本机代码调用)。

回答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 中进行排序。

source link

来源链接

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 更好的聚集索引兼容性。

Flakey for .NET Core

.NET Core 的 Flake

IdGen for .NET Framework

.NET 框架的 IdGen

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