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; only one generator may be registered per application.

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.

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