SequentialGuidGenerator.cs 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164
  1. using System;
  2. using System.Security.Cryptography;
  3. using Abp.Threading.Extensions;
  4. namespace Abp
  5. {
  6. /// <summary>
  7. /// Implements <see cref="IGuidGenerator"/> by creating sequential Guids.
  8. /// This code is taken from https://github.com/jhtodd/SequentialGuid/blob/master/SequentialGuid/Classes/SequentialGuid.cs
  9. /// </summary>
  10. public class SequentialGuidGenerator : IGuidGenerator
  11. {
  12. /// <summary>
  13. /// Gets the singleton <see cref="SequentialGuidGenerator"/> instance.
  14. /// </summary>
  15. public static SequentialGuidGenerator Instance { get; } = new SequentialGuidGenerator();
  16. private static readonly RandomNumberGenerator Rng = RandomNumberGenerator.Create();
  17. public SequentialGuidDatabaseType DatabaseType { get; set; }
  18. /// <summary>
  19. /// Prevents a default instance of the <see cref="SequentialGuidGenerator"/> class from being created.
  20. /// Use <see cref="Instance"/>.
  21. /// </summary>
  22. private SequentialGuidGenerator()
  23. {
  24. DatabaseType = SequentialGuidDatabaseType.SqlServer;
  25. }
  26. public Guid Create()
  27. {
  28. return Create(DatabaseType);
  29. }
  30. public Guid Create(SequentialGuidDatabaseType databaseType)
  31. {
  32. switch (databaseType)
  33. {
  34. case SequentialGuidDatabaseType.SqlServer:
  35. return Create(SequentialGuidType.SequentialAtEnd);
  36. case SequentialGuidDatabaseType.Oracle:
  37. return Create(SequentialGuidType.SequentialAsBinary);
  38. case SequentialGuidDatabaseType.MySql:
  39. return Create(SequentialGuidType.SequentialAsString);
  40. case SequentialGuidDatabaseType.PostgreSql:
  41. return Create(SequentialGuidType.SequentialAsString);
  42. default:
  43. throw new InvalidOperationException();
  44. }
  45. }
  46. public Guid Create(SequentialGuidType guidType)
  47. {
  48. // We start with 16 bytes of cryptographically strong random data.
  49. var randomBytes = new byte[10];
  50. Rng.Locking(r => r.GetBytes(randomBytes));
  51. // An alternate method: use a normally-created GUID to get our initial
  52. // random data:
  53. // byte[] randomBytes = Guid.NewGuid().ToByteArray();
  54. // This is faster than using RNGCryptoServiceProvider, but I don't
  55. // recommend it because the .NET Framework makes no guarantee of the
  56. // randomness of GUID data, and future versions (or different
  57. // implementations like Mono) might use a different method.
  58. // Now we have the random basis for our GUID. Next, we need to
  59. // create the six-byte block which will be our timestamp.
  60. // We start with the number of milliseconds that have elapsed since
  61. // DateTime.MinValue. This will form the timestamp. There's no use
  62. // being more specific than milliseconds, since DateTime.Now has
  63. // limited resolution.
  64. // Using millisecond resolution for our 48-bit timestamp gives us
  65. // about 5900 years before the timestamp overflows and cycles.
  66. // Hopefully this should be sufficient for most purposes. :)
  67. long timestamp = DateTime.UtcNow.Ticks / 10000L;
  68. // Then get the bytes
  69. byte[] timestampBytes = BitConverter.GetBytes(timestamp);
  70. // Since we're converting from an Int64, we have to reverse on
  71. // little-endian systems.
  72. if (BitConverter.IsLittleEndian)
  73. {
  74. Array.Reverse(timestampBytes);
  75. }
  76. byte[] guidBytes = new byte[16];
  77. switch (guidType)
  78. {
  79. case SequentialGuidType.SequentialAsString:
  80. case SequentialGuidType.SequentialAsBinary:
  81. // For string and byte-array version, we copy the timestamp first, followed
  82. // by the random data.
  83. Buffer.BlockCopy(timestampBytes, 2, guidBytes, 0, 6);
  84. Buffer.BlockCopy(randomBytes, 0, guidBytes, 6, 10);
  85. // If formatting as a string, we have to compensate for the fact
  86. // that .NET regards the Data1 and Data2 block as an Int32 and an Int16,
  87. // respectively. That means that it switches the order on little-endian
  88. // systems. So again, we have to reverse.
  89. if (guidType == SequentialGuidType.SequentialAsString && BitConverter.IsLittleEndian)
  90. {
  91. Array.Reverse(guidBytes, 0, 4);
  92. Array.Reverse(guidBytes, 4, 2);
  93. }
  94. break;
  95. case SequentialGuidType.SequentialAtEnd:
  96. // For sequential-at-the-end versions, we copy the random data first,
  97. // followed by the timestamp.
  98. Buffer.BlockCopy(randomBytes, 0, guidBytes, 0, 10);
  99. Buffer.BlockCopy(timestampBytes, 2, guidBytes, 10, 6);
  100. break;
  101. }
  102. return new Guid(guidBytes);
  103. }
  104. /// <summary>
  105. /// Database type to generate GUIDs.
  106. /// </summary>
  107. public enum SequentialGuidDatabaseType
  108. {
  109. SqlServer,
  110. Oracle,
  111. MySql,
  112. PostgreSql,
  113. }
  114. /// <summary>
  115. /// Describes the type of a sequential GUID value.
  116. /// </summary>
  117. public enum SequentialGuidType
  118. {
  119. /// <summary>
  120. /// The GUID should be sequential when formatted using the
  121. /// <see cref="Guid.ToString()" /> method.
  122. /// </summary>
  123. SequentialAsString,
  124. /// <summary>
  125. /// The GUID should be sequential when formatted using the
  126. /// <see cref="Guid.ToByteArray" /> method.
  127. /// </summary>
  128. SequentialAsBinary,
  129. /// <summary>
  130. /// The sequential portion of the GUID should be located at the end
  131. /// of the Data4 block.
  132. /// </summary>
  133. SequentialAtEnd
  134. }
  135. }
  136. }