Skip to content

v6.2.0 — Bulk generation, TimeProvider clocks, and EF Core value generation

Latest

Choose a tag to compare

@buvinghausen buvinghausen released this 10 Jun 15:26
fc82ce7

Highlights

  • 🚀 Bulk generationGuidV7.Fill(Span<Guid>) / NewGuids(count) (and the GuidV8Time mirrors) reserve one counter block,
    capture one timestamp, and make one RNG call for the whole batch. Roughly 11× faster than calling Guid.CreateVersion7 in a
    loop, and still zero-alloc.
  • 🕰 TimeProvider overloads — every generation path, single-call and bulk, now accepts an injected clock on .NET 8+.
    FakeTimeProvider-style testing without threading raw timestamps through your code.
  • 🗝 EF Core value generation — one line in ConfigureConventions and every Guid, SequentialGuid, and SequentialSqlGuid
    primary key gets a real RFC 9562 v7 value client-side on Add. No database round-trip, retry-safe, composite keys included, and
    explicit per-property configuration always wins.
  • 🍃 MongoDBMongoSequentialGuidGenerator can now emit v7 ids via new MongoSequentialGuidGenerator(SequentialGuidType.Rfc9562V7). Instance still emits v8 — no silent behavior change.

New APIs

Core (SequentialGuid, .NET 8+ assets):

  • GuidV7.Fill(Span<Guid>) / FillSql(Span<Guid>) / NewGuids(int) / NewSqlGuids(int) — each with no-arg, long unixMilliseconds, and TimeProvider timestamp forms
  • GuidV8Time.Fill / FillSql / NewGuids / NewSqlGuids — same surface with DateTime as the deterministic form
  • GuidV7.NewGuid(TimeProvider) / NewSqlGuid(TimeProvider) and the GuidV8Time equivalents

Batches that would exceed the counter space (2²⁶ for v7, 2²² for v8) throw ArgumentOutOfRangeException — no silent wrap, no
out-of-order ids.

EF Core (SequentialGuid.EntityFrameworkCore):

protected override void ConfigureConventions(ModelConfigurationBuilder configurationBuilder)
{
    configurationBuilder.AddSequentialGuidValueConverters();
    configurationBuilder.UseSequentialGuidValueGeneration();
    // or: UseSequentialGuidValueGeneration(sqlServerByteOrder: true);
}

The four generators (SequentialGuidValueGenerator, SequentialSqlGuidValueGenerator,
SequentialGuidStructValueGenerator, SequentialSqlGuidStructValueGenerator) are public for
explicit HasValueGenerator<T>() wiring. Includes a sentinel pin that works around EF 8/9
computing struct sentinels via Activator.CreateInstance (fixed in EF 10).

Performance

Measured with BenchmarkDotNet on Intel Core Ultra 9 185H, .NET 10.0.9 — regenerate with
dotnet run -c Release --project benches/Benchmarks -- --filter *BclComparison*:

┌──────────────────────────────────┬──────────────┬──────────┬───────────┐
│              Method              │     Mean     │  Ratio   │ Allocated │
├──────────────────────────────────┼──────────────┼──────────┼───────────┤
│ Guid.NewGuid                     │     44.46 ns │     1.00- │
├──────────────────────────────────┼──────────────┼──────────┼───────────┤
│ Guid.CreateVersion7              │     65.09 ns │     1.46- │
├──────────────────────────────────┼──────────────┼──────────┼───────────┤
│ GuidV7.NewGuid                   │     82.59 ns │     1.86- │
├──────────────────────────────────┼──────────────┼──────────┼───────────┤
│ 'Guid.CreateVersion7 ×1000 loop' │ 67,284.17 ns │ 1,513.81- │
├──────────────────────────────────┼──────────────┼──────────┼───────────┤
│ 'GuidV7.NewGuid ×1000 loop'      │ 88,345.82 ns │ 1,987.67- │
├──────────────────────────────────┼──────────────┼──────────┼───────────┤
│ 'GuidV7.Fill ×1000 bulk'         │  6,210.75 ns │   139.73- │
└──────────────────────────────────┴──────────────┴──────────┴───────────┘

The trade-off is honest: the monotonic counter and SQL-capable layout cost a few nanoseconds per
single call versus the BCL's Guid.CreateVersion7. What you get for it: strict same-millisecond
ordering under concurrency, SQL Server byte-order support, timestamp round-tripping, .NET
Framework / .NET Standard reach — and where throughput matters, the bulk API more than repays it.
The README has a new Why not Guid.CreateVersion7? (https://github.com/buvinghausen/SequentialGuid#why-not-guidcreateversion7)
section with the full comparison.

Fixes

- 🎲 The legacy-TFM GetInt32 counter-seeding helper now uses unbiased mask-and-reject sampling
(it previously combined GetNonZeroBytes with a double-modulo fold — harmless in practice
since it only seeded startup counters, but biased nonetheless).

Compatibility

- SemVer minor — fully additive. No breaking changes, no behavior changes to existing APIs.
- Bulk generation and TimeProvider overloads ship in the .NET 8+ assets; .NET Framework 4.6.2
and .NET Standard 2.0 consumers keep the existing single-call surface.
- The EF Core package targets EF Core 8 / 9 / 10 on net8.0 / net9.0 / net10.0.
- MongoSequentialGuidGenerator.Instance still emits v8 time-based GUIDs.

Verified

- 31,605 tests green across net11.0 / net10.0 / net9.0 / net8.0 / net472, zero warnings with
warnings-as-errors.
- Native AOT smoke tests (core + NodaTime, and a new EF Core smoke app) publish and run in CI.
- Bulk/single-call interleaving is regression-tested with deterministic timestamps on both
time-based generators — counter blocks and single-call slots provably never overlap.

Full Changelog: https://github.com/buvinghausen/SequentialGuid/compare/v6.1.0...v6.2.0