54

レコードを挿入してラウンドトリップで読み戻したり、ネイティブのwin dll呼び出しを呼び出したりせずに、SQL Server 2005+ Sequential Guidジェネレーターの機能を取得する方法はありますか?誰かがrpcrt4.dllを使用する方法で答えるのを見ましたが、それが私のホストされた環境から本番環境で機能するかどうかはわかりません。

編集: @John Bokerの回答を使用して、最初からやり直す以外に、最後に生成されたGuidに依存するのではなく、GuidCombジェネレーターに変換しようとしました。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が作成されるときはいつでも、アプリケーションのリサイクル中にのみ発生するため、ページングの中断になることについてはあまり心配していません。

4

11 に答える 11

76

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 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 のは、 からのタイプ 1 uuidUuidCreateSequentialの単なる実装です。RFC 4122

uuid には 3 つの重要な部分があります。

  • node: (6 バイト) - コンピュータの MAC アドレス
  • timestamp: (7 バイト) - 1582 年 10 月 15 日 00:00:00.00 (西暦がグレゴリオ暦に改められた日) からの 100 ns 間隔の数
  • clockSequenceNumber(2 バイト) - GUID を 100ns より速く生成した場合、または MAC アドレスを変更した場合のカウンター

基本的なアルゴリズムは次のとおりです。

  1. システム全体のロックを取得する
  2. 最後の を読み取り、node永続timestampストレージclockSequenceNumber(レジストリ/ファイル) から
  3. 現在のnode(つまり、MAC アドレス)を取得します。
  4. 電流を取得するtimestamp
    • a) 保存された状態が利用できないか破損している場合、または MAC アドレスが変更されている場合は、ランダムなclockSequenceNumber
    • b) 状態が利用可能であるが、現在の状態がtimestamp保存されたタイムスタンプと同じか古い場合は、clockSequenceNumber
  5. 保存しnode、永続ストレージtimestampclockSequenceNumber戻る
  6. グローバルロックを解放する
  7. rfcに従ってguid構造をフォーマットします

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

: 完全にテストされていません。私はRFCからそれを目の当たりにしました。

  • バイト オーダーを変更する必要がある場合があります ( SQL サーバーのバイト オーダーは次のとおりです) 。
  • バージョン 6 (バージョン 1 ~ 5 が定義されています) など、独自のバージョンを作成することもできます。そうすれば、あなたは普遍的にユニークであることが保証されます
于 2012-03-02T19:00:12.450 に答える
27

この人はシーケンシャル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;
        }
    }
}
于 2009-11-17T21:39:23.250 に答える
20

NHibernate が Guid.Comb アルゴリズムを実装する方法は次のとおりです

private Guid GenerateComb()
{
    byte[] guidArray = Guid.NewGuid().ToByteArray();

    DateTime baseDate = new DateTime(1900, 1, 1);
    DateTime now = DateTime.UtcNow;

    // 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);
}
于 2014-08-24T14:41:21.937 に答える
7

頻繁に (1 ミリ秒あたり少なくとも 3 回) 更新される Sequential GUID は、こちらにあります。通常の C# コードで作成されます (ネイティブ コードの呼び出しはありません)。

于 2013-11-01T21:04:22.400 に答える
6

他の提案と比較すると興味深いかもしれません:

EntityFramework Core は、sequentialGuidValueGenerator も実装します。値ごとに乱数の GUID を生成し、SQL Server での並べ替えのために、タイムスタンプとスレッド セーフなインクリメントに基づいて最上位バイトのみを変更します。

ソースリンク

これにより、値はすべて大きく異なりますが、タイムスタンプはソート可能です。

于 2016-10-03T15:52:14.663 に答える
3

私のソリューション(VBですが、変換は簡単です)。GUIDの最も重要な(SQL Serverの並べ替えの場合)最初の8バイトを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
于 2013-02-26T10:45:20.737 に答える
2

私が知る限り、NHibernate には GuidCombGenerator と呼ばれる特別なジェネレーターがあります。あなたはそれを見ることができます。

于 2009-11-17T21:53:35.137 に答える