Skip to main content
Version: Next

GUID Generation

Overview

RCommon abstracts GUID generation behind an IGuidGenerator interface for two reasons:

  1. Testability. Code that calls Guid.NewGuid() directly cannot be controlled in unit tests. Injecting IGuidGenerator lets tests substitute a predictable implementation.
  2. Database performance. Standard random GUIDs cause index fragmentation in clustered indexes because their values are not monotonically increasing. Sequential GUIDs embed a timestamp in the byte layout so new rows are always appended at the end of the index.

RCommon ships two implementations out of the box: SimpleGuidGenerator and SequentialGuidGenerator. You pick one during application configuration; see Behavior on repeated calls below for what happens when more than one module configures GUID generation.

Installation

NuGet Package
dotnet add package RCommon.Core

Configuration

Configure GUID generation inside the AddRCommon() builder chain.

Simple (random) GUIDs

Uses Guid.NewGuid() internally. Suitable for non-database use cases or when the underlying database handles GUID ordering itself (e.g., NEWSEQUENTIALID() in SQL Server).

builder.Services.AddRCommon()
.WithSimpleGuidGenerator();

Sequential GUIDs

Generates GUIDs with an embedded timestamp so that successive calls produce values that sort in the correct order for a given database platform. The byte layout varies by platform; choose the appropriate SequentialGuidType for your database:

SequentialGuidTypeDatabase
SequentialAsStringMySQL, PostgreSQL
SequentialAsBinaryOracle
SequentialAtEndSQL Server (default)
builder.Services.AddRCommon()
.WithSequentialGuidGenerator(options =>
{
options.DefaultSequentialGuidType = SequentialGuidType.SequentialAtEnd;
});

When DefaultSequentialGuidType is left null, the generator defaults to SequentialAtEnd.

Behavior on repeated calls

GUID generator verbs (WithSimpleGuidGenerator, WithSequentialGuidGenerator) now follow singleton-style modular composition: calling the same generator twice is an idempotent no-op, while calling a different generator after one is already configured throws RCommonBuilderException. The exception message names both impl types and offers a remediation hint. See Modular Composition for the full conflict matrix.

Usage

Inject IGuidGenerator wherever you need to create a new identifier:

using RCommon;

public class OrderService
{
private readonly IGuidGenerator _guidGenerator;

public OrderService(IGuidGenerator guidGenerator)
{
_guidGenerator = guidGenerator;
}

public Order CreateOrder(CustomerId customerId)
{
var orderId = _guidGenerator.Create();
return new Order(orderId, customerId);
}
}

Generating a specific sequential GUID type at runtime

SequentialGuidGenerator exposes an overload that accepts a SequentialGuidType directly, which is useful when a single application works with multiple databases:

// Only available when you have a reference to SequentialGuidGenerator directly
var generator = serviceProvider.GetRequiredService<IGuidGenerator>() as SequentialGuidGenerator;
var guid = generator?.Create(SequentialGuidType.SequentialAsString);

For most scenarios, rely on the default configured via WithSequentialGuidGenerator and inject IGuidGenerator — the concrete type is an implementation detail.

In unit tests

Substitute IGuidGenerator with a test double that returns a known value:

public class FakeGuidGenerator : IGuidGenerator
{
private readonly Guid _value;

public FakeGuidGenerator(Guid value)
{
_value = value;
}

public Guid Create() => _value;
}

// In a test
var knownId = Guid.Parse("00000000-0000-0000-0000-000000000001");
var service = new OrderService(new FakeGuidGenerator(knownId));
var order = service.CreateOrder(customerId);
Assert.Equal(knownId, order.Id);

API Summary

IGuidGenerator

MemberDescription
Guid Create()Creates and returns a new Guid.

SimpleGuidGenerator

Registered by WithSimpleGuidGenerator(). Calls Guid.NewGuid() on every invocation. Scoped lifetime.

SequentialGuidGenerator

Registered by WithSequentialGuidGenerator(...). Transient lifetime. Generates a GUID composed of 10 bytes of cryptographically random data and a 6-byte millisecond-resolution timestamp. Byte layout is controlled by SequentialGuidType.

SequentialGuidGeneratorOptions

PropertyTypeDefaultDescription
DefaultSequentialGuidTypeSequentialGuidType?null (resolves to SequentialAtEnd)The byte layout strategy used when Create() is called without an explicit type.

SequentialGuidType enum

ValueDescription
SequentialAsStringTimestamp at start; sequential when formatted as a string. Use with MySQL, PostgreSQL.
SequentialAsBinaryTimestamp at start; sequential when compared as a byte array. Use with Oracle.
SequentialAtEndTimestamp at end of the Data4 block. Use with SQL Server.
RCommonRCommon