diff --git a/src/NHibernate.Test/IdGen/GuidComb/GuidCombFixture.cs b/src/NHibernate.Test/IdGen/GuidComb/GuidCombFixture.cs new file mode 100644 index 00000000000..b79bbb807b1 --- /dev/null +++ b/src/NHibernate.Test/IdGen/GuidComb/GuidCombFixture.cs @@ -0,0 +1,32 @@ +using System; +using NHibernate.Id; +using NUnit.Framework; + +namespace NHibernate.Test.IdGen.GuidComb; + +[TestFixture] +public class GuidCombFixture +{ + class GuidCombGeneratorEx : GuidCombGenerator + { + public static Guid Generate(string guid, DateTime utcNow) => GenerateComb(Guid.Parse(guid), utcNow); + } + + [Test] + public void CanGenerateSequentialGuid() + { + Assert.AreEqual(Guid.Parse("076a04fa-ef4e-4093-8479-b0e10103cdc5"), + GuidCombGeneratorEx.Generate( + "076a04fa-ef4e-4093-8479-8599e96f14cf", + new DateTime(2023, 12, 23, 15, 45, 55, DateTimeKind.Utc)), + "seed: 076a04fa"); + + Assert.AreEqual(Guid.Parse("81162ee2-a4cb-4611-9327-d61f0137e5b6"), + GuidCombGeneratorEx.Generate( + "81162ee2-a4cb-4611-9327-23bbda36176c", + new DateTime(2050, 01, 29, 18, 55, 35, DateTimeKind.Utc)), + "seed: 81162ee2"); + + } + +} diff --git a/src/NHibernate/Async/Id/GuidCombGenerator.cs b/src/NHibernate/Async/Id/GuidCombGenerator.cs index 59540f3d2ca..eb4ef6c7c63 100644 --- a/src/NHibernate/Async/Id/GuidCombGenerator.cs +++ b/src/NHibernate/Async/Id/GuidCombGenerator.cs @@ -9,6 +9,7 @@ using System; +using System.Diagnostics; using NHibernate.Engine; namespace NHibernate.Id diff --git a/src/NHibernate/Id/GuidCombGenerator.cs b/src/NHibernate/Id/GuidCombGenerator.cs index cb01227c979..adbbd78196d 100644 --- a/src/NHibernate/Id/GuidCombGenerator.cs +++ b/src/NHibernate/Id/GuidCombGenerator.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics; using NHibernate.Engine; namespace NHibernate.Id @@ -36,34 +37,32 @@ public partial class GuidCombGenerator : IIdentifierGenerator /// The new identifier as a . public object Generate(ISessionImplementor session, object obj) { - return GenerateComb(); + return GenerateComb(Guid.NewGuid(), DateTime.UtcNow); } /// /// Generate a new using the comb algorithm. /// - private Guid GenerateComb() + protected static Guid GenerateComb(Guid guid, DateTime utcNow) { - byte[] guidArray = Guid.NewGuid().ToByteArray(); - - DateTime now = DateTime.UtcNow; - +#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP2_1_OR_GREATER + Span guidArray = stackalloc byte[16]; + guid.TryWriteBytes(guidArray); +#else + var guidArray = guid.ToByteArray(); +#endif // Get the days and milliseconds which will be used to build the byte string - TimeSpan days = new TimeSpan(now.Ticks - BaseDateTicks); - TimeSpan msecs = now.TimeOfDay; - - // Convert to a byte array + var ts = new TimeSpan(utcNow.Ticks - BaseDateTicks); + var days = ts.Days; + guidArray[10] = (byte) (days >> 8); + guidArray[11] = (byte) days; + // 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); + var msecs = (long) (utcNow.TimeOfDay.TotalMilliseconds / 3.333333); + guidArray[12] = (byte) (msecs >> 24); + guidArray[13] = (byte) (msecs >> 16); + guidArray[14] = (byte) (msecs >> 8); + guidArray[15] = (byte) msecs; return new Guid(guidArray); }