SkillAgentSearch skills...

Ulid

A .NET C# library for generating and working with Universally Unique Lexicographically Sortable Identifiers (ULIDs), designed to be globally unique, sortable, human-readable, and AoT compatible, making them ideal for use in distributed systems and databases.

Install / Use

/learn @ByteAether/Ulid
About this skill

Quality Score

0/100

Supported Platforms

Universal

README

ULID from ByteAether

License NuGet Version NuGet Downloads GitHub Build Status GitHub Security

A high-performance, fully compliant .NET implementation of ULIDs (Universally Unique Lexicographically Sortable Identifiers), adhering to the official ULID specification.

Table of Contents

Introduction

<img align="right" width="100px" src="assets/logo.png" />

ULIDs are universally unique, lexicographically sortable identifiers, ideal for distributed systems and time-ordered data due to their sortability and human-readability—advantages GUIDs lack. This library offers a robust, fully compliant .NET implementation, addressing limitations found in other ULID solutions.

This implementation addresses a potential OverflowException that can occur when generating multiple ULIDs within the same millisecond due to the "random" part overflowing. To ensure dependable, unique ULID generation, our solution increments the timestamp component upon random part overflow, eliminating such exceptions. This behavior aligns with discussions in ULID specification issue #39.

This library uniquely addresses the predictability of monotonic ULIDs generated within the same millisecond by allowing random increments to the random component. This mitigates enumeration attack vulnerabilities, as discussed in ULID specification issue #105. You can configure the random increment with a random value ranging from 1-byte (1–256) to 4-bytes (1–4,294,967,296), enhancing randomness while preserving lexicographical sortability.

ULID vs UUIDv7

In the evolution of distributed identifiers, ULIDs represent the definitive successor to both legacy GUIDs and auto-incrementing integers. While modern standards like UUIDv7 attempt to address sortability, the RFC 9562 makes monotonicity optional, allowing implementations (such as the native .NET provider) to sacrifice strict ordering during sub-millisecond bursts. This "lazy" approach reintroduces the very index fragmentation and out-of-order writes that sortable IDs were meant to solve.

ULID addresses this by design, mandating strict lexicographical sortability and monotonic increments. By enforcing these requirements at the specification level rather than leaving them to the implementor's discretion, ULID ensures consistent, high-performance behavior across all environments. This library provides a robust, compliant implementation that guarantees this order, enabling your application to scale without the performance trade-offs of non-deterministic identifiers.

Features

.NET AOT Ready .NET 10.0 .NET 9.0 .NET 8.0 .NET 7.0 .NET 6.0 .NET 5.0 .NET Standard 2.1 .NET Standard 2.0

  • Universally Unique: Ensures global uniqueness across systems.
  • Sortable: Lexicographically ordered for time-based sorting.
  • Lock-Free Synchronization: Monotonic generation utilizes a high-performance, lock-free compare-and-exchange (CAS) approach.
  • Specification-Compliant: Fully adheres to the ULID specification.
  • Interoperable: Includes conversion methods to and from GUIDs, Crockford's Base32 strings, and byte arrays.
  • Ahead-of-Time (AoT) Compilation Compatible: Fully compatible with AoT compilation for improved startup performance and smaller binary sizes.
  • Error-Free Generation: Prevents OverflowException by incrementing the timestamp component when the random part overflows, ensuring continuous unique ULID generation.

These features collectively make ByteAether.Ulid a robust and efficient choice for managing unique identifiers in your .NET applications.

Installation

Install the latest stable package via NuGet:

dotnet add package ByteAether.Ulid

To install a specific preview version, use the --version option:

dotnet add package ByteAether.Ulid --version <VERSION_NUMBER>

Usage

Here is a basic example of how to use the ULID implementation:

using System;
using ByteAether.Ulid;

// Create a new ULID
var ulid = Ulid.New();

// Convert to byte array and back
byte[] byteArray = ulid.ToByteArray();
var ulidFromByteArray = Ulid.New(byteArray);

// Convert to GUID and back
Guid guid = ulid.ToGuid();
var ulidFromGuid = Ulid.New(guid);

// Convert to string and back
string ulidString = ulid.ToString();
var ulidFromString = Ulid.Parse(ulidString);

Console.WriteLine($"ULID: {ulid}, GUID: {guid}, String: {ulidString}");

Filtering by Time Range (LINQ)

Since ULIDs are lexicographically sortable and contain a timestamp, you can use Ulid.MinAt() and Ulid.MaxAt() to generate boundary ULIDs for a specific time range. This allows EF Core to translate these into efficient range comparisons (e.g., WHERE Id >= @min AND Id <= @max) in your database.

public async Task<List<Entity>> GetEntitiesFromYesterday(MyDbContext context)
{
    var startOfYesterday = DateTimeOffset.UtcNow.AddDays(-1).Date;
    var endOfYesterday = startOfYesterday.AddDays(1).AddTicks(-1);

    // Create boundary ULIDs for the time range
    var minUlid = Ulid.MinAt(startOfYesterday);
    var maxUlid = Ulid.MaxAt(endOfYesterday);

    // This query uses the primary key index for high performance
    return await context.Entities
        .Where(e => e.Id >= minUlid && e.Id <= maxUlid)
        .ToListAsync();
}

Advanced Generation

You can customize ULID generation by providing GenerationOptions. This allows you to control monotonicity and the source of randomness.

Example: Monotonic ULID with Random Increments

To generate ULIDs that are monotonically increasing with a random increment, you can specify the Monotonicity option.

using System;
using ByteAether.Ulid;
using static ByteAether.Ulid.Ulid.GenerationOptions;

// Configure options for a 2-byte random increment
var options = new Ulid.GenerationOptions
{
	Monotonicity = MonotonicityOptions.MonotonicRandom2Byte
};

// Generate a ULID with the specified options
var ulid = Ulid.New(options);

Console.WriteLine($"ULID with random increment: {ulid}");

Example: Setting Default Generation Options

You can set default generation options for the entire application. This is useful for consistently applying specific behaviors, such as prioritizing performance over cryptographic security.

using System;
using ByteAether.Ulid;
using static ByteAether.Ulid.Ulid.GenerationOptions;

// Set default generation options for the entire application
Ulid.DefaultGenerationOptions = new()
{
	Monotonicity = MonotonicityOptions.MonotonicIncrement,
	InitialRandomSource = new PseudoRandomProvider(),
	IncrementRandomSource = new PseudoRandomProvider()
};

// Now, any subsequent call to Ulid.New() will use these options
var ulid = Ulid.New();

Console.WriteLine($"ULID from pseudo-random source: {ulid}");

API

The Ulid implementation provides the following properties and methods:

Creation

  • Ulid.New(GenerationOptions? options = null)
    Generates a new ULID using default generation options. Accepts an optional GenerationOptions parameter to customize the generation behavior.
  • Ulid.New(DateTimeOffset dateTimeOffset, GenerationOptions? options = null)
    Generates a new ULID using the specified DateTimeOffset and default generation options. Accepts an optional GenerationOptions parameter to customize the generation behavior.
  • Ulid.New(long timestamp, GenerationOptions? options = null)
    Generates a new ULID using the specified Unix timestamp in milliseconds (long) and default generation options. Accepts an optional GenerationOptions parameter to customize the generation behavior.
  • Ulid.New(DateTimeOffset dateTimeOffset, ReadOnlySpan<byte> random)
    Generates a new ULID using the specified DateTimeOffset and a pre-existing random byte array.
  • `Ulid.New(long ti

Related Skills

View on GitHub
GitHub Stars72
CategoryData
Updated3d ago
Forks3

Languages

C#

Security Score

100/100

Audited on Mar 25, 2026

No findings