Skip to content

Commit

Permalink
Merge pull request #440 from MUnique/dev/guidv7
Browse files Browse the repository at this point in the history
Implemented UUID V7 for primary key values
  • Loading branch information
sven-n authored Aug 6, 2024
2 parents a7ce845 + 234dfde commit c1054c3
Show file tree
Hide file tree
Showing 5 changed files with 79 additions and 4 deletions.
2 changes: 1 addition & 1 deletion src/GameLogic/MiniGames/MiniGameContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -494,7 +494,7 @@ protected async ValueTask SaveRankingAsync(IEnumerable<(int Rank, Character Char
try
{
using var context = this._gameContext.PersistenceContextProvider.CreateNewTypedContext<MiniGameRankingEntry>(false);
var instanceId = Guid.NewGuid();
var instanceId = GuidV7.NewGuid();
var timestamp = DateTime.UtcNow;
foreach (var score in scoreEntries)
{
Expand Down
1 change: 1 addition & 0 deletions src/Persistence/EntityFramework/EntityDataContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ protected override void OnModelCreating(ModelBuilder modelBuilder)
if (key != null)
{
key.ValueGenerated = Microsoft.EntityFrameworkCore.Metadata.ValueGenerated.OnAdd;
key.SetValueGeneratorFactory((_, _) => new GuidV7ValueGenerator());
}
}

Expand Down
23 changes: 23 additions & 0 deletions src/Persistence/EntityFramework/GuidV7ValueGenerator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// <copyright file="GuidV7ValueGenerator.cs" company="MUnique">
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
// </copyright>

namespace MUnique.OpenMU.Persistence.EntityFramework;

using Microsoft.EntityFrameworkCore.ChangeTracking;
using Microsoft.EntityFrameworkCore.ValueGeneration;

/// <summary>
/// A value generator for UUID V7 used as primary key.
/// </summary>
public class GuidV7ValueGenerator : ValueGenerator<Guid>
{
/// <inheritdoc/>
public override bool GeneratesTemporaryValues => false;

/// <inheritdoc/>
public override Guid Next(EntityEntry entry)
{
return GuidV7.NewGuid();
}
}
48 changes: 48 additions & 0 deletions src/Persistence/GuidV7.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// <copyright file="GuidV7.cs" company="MUnique">
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
// </copyright>

namespace MUnique.OpenMU.Persistence;

using System.Security.Cryptography;

/// <summary>
/// A generator for version 7 GUIDs. Layout:
/// The first 6 bytes is a unix timestamp in milliseconds.
/// The 7th byte contains the version number (7) in the upper 4 bits.
/// The rest is random.
/// </summary>
public static class GuidV7
{
/// <summary>
/// Creates a new random guid.
/// </summary>
/// <returns>The new guid.</returns>
public static Guid NewGuid() => NewGuid(DateTimeOffset.UtcNow);

/// <summary>
/// Creates a new random guid for the specified date.
/// </summary>
/// <param name="dateTimeOffset">The date time offset which is the prefix of the id.</param>
/// <returns>The new guid.</returns>
public static Guid NewGuid(DateTimeOffset dateTimeOffset)
{
// We create a buffer which is two bytes bigger than the Guid,
// because we don't need the first two bytes of the timestamp.
Span<byte> buffer = stackalloc byte[18];
var uuidAsBytes = buffer[2..];
var currentTimestamp = dateTimeOffset.ToUnixTimeMilliseconds();

if (!BitConverter.TryWriteBytes(buffer, currentTimestamp))
{
throw new InvalidOperationException("Could not convert the timestamp to bytes.");
}

RandomNumberGenerator.Fill(uuidAsBytes[6..]);

uuidAsBytes[6] &= 0x0F;
uuidAsBytes[6] |= 0x70;

return new Guid(uuidAsBytes, true);
}
}
9 changes: 6 additions & 3 deletions src/Persistence/InMemory/InMemoryContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,10 @@ public void Dispose()
this.SavedChanges = null;
}

/// <inheritdoc/>
/// <summary>
/// Saves the changes of the context.
/// </summary>
/// <returns><c>True</c>, if the saving was successful; <c>false</c>, otherwise.</returns>
public bool SaveChanges()
{
foreach (var repository in this.Provider.MemoryRepositories)
Expand Down Expand Up @@ -98,7 +101,7 @@ public T CreateNew<T>(params object?[] args)
{
if (identifiable.Id == Guid.Empty)
{
identifiable.Id = Guid.NewGuid();
identifiable.Id = GuidV7.NewGuid();
}

var repository = this.Provider.GetRepository<T>() as IMemoryRepository;
Expand All @@ -116,7 +119,7 @@ public object CreateNew(Type type, params object?[] args)
{
if (identifiable.Id == Guid.Empty)
{
identifiable.Id = Guid.NewGuid();
identifiable.Id = GuidV7.NewGuid();
}

var repository = this.Provider.GetRepository(type) as IMemoryRepository;
Expand Down

0 comments on commit c1054c3

Please sign in to comment.