// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; using System.Collections.Generic; using EntityFrameworkCore.Jet.Data; using System.Linq; using System.Threading; using System.Threading.Tasks; using EntityFrameworkCore.Jet.FunctionalTests.TestUtilities; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.TestUtilities; using Microsoft.Extensions.DependencyInjection; using Xunit; using Microsoft.EntityFrameworkCore.ChangeTracking; using Microsoft.EntityFrameworkCore.ValueGeneration; using System.Diagnostics.Metrics; using System.Runtime.InteropServices; #nullable disable // ReSharper disable InconsistentNaming namespace EntityFrameworkCore.Jet.FunctionalTests { public class SequentialGuidEndToEndTest : IAsyncLifetime { [ConditionalFact] public async Task Can_use_sequential_GUID_end_to_end_async() { var serviceProvider = new ServiceCollection() .AddEntityFrameworkJet() .BuildServiceProvider(); using (var context = new BronieContext(serviceProvider, TestStore.Name)) { context.Database.EnsureCreatedResiliently(); for (var i = 0; i < 50; i++) { await context.AddAsync( new Pegasus { Name = "Rainbow Dash " + i }); } await context.SaveChangesAsync(); } using (var context = new BronieContext(serviceProvider, TestStore.Name)) { var pegasuses = await context.Pegasuses.OrderBy(e => e.Id).ToListAsync(); for (var i = 0; i < 50; i++) { Assert.Equal("Rainbow Dash " + i, pegasuses[i].Name); } } } [ConditionalFact] public async Task Can_use_explicit_values() { var serviceProvider = new ServiceCollection() .AddEntityFrameworkJet() .BuildServiceProvider(); var guids = new List(); using (var context = new BronieContext(serviceProvider, TestStore.Name)) { context.Database.EnsureCreatedResiliently(); for (var i = 0; i < 50; i++) { guids.Add( context.Add( new Pegasus { Name = "Rainbow Dash " + i, Index = i, Id = Guid.NewGuid() }).Entity.Id); } await context.SaveChangesAsync(); } using (var context = new BronieContext(serviceProvider, TestStore.Name)) { var pegasuses = await context.Pegasuses.OrderBy(e => e.Index).ToListAsync(); for (var i = 0; i < 50; i++) { Assert.Equal("Rainbow Dash " + i, pegasuses[i].Name); Assert.Equal(guids[i], pegasuses[i].Id); } } } private class BronieContext(IServiceProvider serviceProvider, string databaseName) : DbContext { public DbSet Pegasuses { get; set; } protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) => optionsBuilder .UseJet(JetTestStore.CreateConnectionString(databaseName), TestEnvironment.DataAccessProviderFactory, b => b.ApplyConfiguration()) .UseInternalServiceProvider(serviceProvider); } private class Pegasus { public Guid Id { get; set; } public string Name { get; set; } public int Index { get; set; } } protected JetTestStore TestStore { get; private set; } public async Task InitializeAsync() => TestStore = await JetTestStore.CreateInitializedAsync("SequentialGuidEndToEndTest"); public async Task DisposeAsync() => await TestStore.DisposeAsync(); [ConditionalFact] public void CustomUuid7Test() { DateTimeOffset dtoNow = DateTimeOffset.UtcNow; Guid net9internal = Guid.CreateVersion7(dtoNow); Guid custom = Next(dtoNow); var bytenet9 = net9internal.ToByteArray().AsSpan(0, 6); var bytecustom = custom.ToByteArray().AsSpan(0,6); Assert.Equal(bytenet9,bytecustom); Assert.Equal(net9internal.Version,custom.Version); var t1 = net9internal.Variant & Variant10xxMask; var t2 = BitConverter.GetBytes(custom.Variant); Assert.InRange(net9internal.Variant,8,0xB); Assert.InRange(custom.Variant, 8, 0xB); } private const byte Variant10xxValue = 0x80; private const ushort Version7Value = 0x7000; private const ushort VersionMask = 0xF000; private const byte Variant10xxMask = 0xC0; private Guid Next(DateTimeOffset timeStamp) { Span guidBytes = stackalloc byte[16]; var succeeded = Guid.NewGuid().TryWriteBytes(guidBytes); var unixms = timeStamp.ToUnixTimeMilliseconds(); Span counterBytes = stackalloc byte[sizeof(long)]; MemoryMarshal.Write(counterBytes, in unixms); if (!BitConverter.IsLittleEndian) { counterBytes.Reverse(); } //unix ts ms - 48 bits (6 bytes) guidBytes[00] = counterBytes[2]; guidBytes[01] = counterBytes[3]; guidBytes[02] = counterBytes[4]; guidBytes[03] = counterBytes[5]; guidBytes[04] = counterBytes[0]; guidBytes[05] = counterBytes[1]; //UIDv7 version - first 4 bits (1/2 byte) of the next 16 bits (2 bytes) var _c = BitConverter.ToInt16(guidBytes.Slice(6, 2)); _c = (short)((_c & ~VersionMask) | Version7Value); BitConverter.TryWriteBytes(guidBytes.Slice(6, 2), _c); //2 bit variant //first 2 bits of the next 64 bits (8 bytes) guidBytes[8] = (byte)((guidBytes[8] & ~Variant10xxMask) | Variant10xxValue); return new Guid(guidBytes); } } }