{
  "version": "https://jsonfeed.org/version/1.1",
  "title": "ByteAether",
  "language": "en",
  "home_page_url": "https://byteaether.github.io/",
  "feed_url": "https://byteaether.github.io/feed/feed.json",
  "description": "Decoding complexity to craft beautiful simplicity.",
  "authors": [
    {
      "name": "Joonatan Uusväli"
    }
  ],
  "items": [
    {
      "id": "https://byteaether.github.io/2026/byteaetherulid-v142-expands-the-net-ecosystem-with-aspnet-core-and-cli-support/",
      "url": "https://byteaether.github.io/2026/byteaetherulid-v142-expands-the-net-ecosystem-with-aspnet-core-and-cli-support/",
      "title": "ByteAether.Ulid v1.4.2 Expands the .NET Ecosystem with ASP.NET Core and CLI Support",
      "content_html": "<p>A production-grade identifier library requires tight integration across data persistence layers, API boundary definitions, and operational tooling. <a href=\"https://github.com/ByteAether/Ulid\" target=\"_blank\" rel=\"noopener noreferrer\">ByteAether.Ulid</a> implements a high-performance, specification-compliant .NET type for the <a href=\"https://github.com/ulid/spec\" target=\"_blank\" rel=\"noopener noreferrer\">ULID standard</a>, engineering strict monotonicity, throughput optimization, and deterministic ordering under high-concurrency workloads.</p><p>The <a href=\"https://github.com/ByteAether/Ulid/releases/tag/v1.4.2\" target=\"_blank\" rel=\"noopener noreferrer\">ByteAether.Ulid v1.4.2</a> release extends the ecosystem beyond the core primitive. Alongside existing data-access integrations, the repository (available on <a href=\"https://github.com/ByteAether/Ulid\" target=\"_blank\" rel=\"noopener noreferrer\">GitHub</a>) now provides two specialized packages targeting modern .NET architectures:</p><ul><li><p><a href=\"https://www.nuget.org/packages/ByteAether.Ulid.AspNetCore/\" target=\"_blank\" rel=\"noopener noreferrer\">ByteAether.Ulid.AspNetCore</a>: Native route constraints, System.Text.Json converters, and OpenAPI schema generators.</p></li><li><p><a href=\"https://www.nuget.org/packages/ByteAether.Ulid.Cli/\" target=\"_blank\" rel=\"noopener noreferrer\">ByteAether.Ulid.Cli</a>: A cross-platform global tool for generating, parsing, and inspecting ULIDs within shell scripts and operational pipelines.</p></li></ul><p>These abstractions eliminate the need to maintain bespoke serialization layers or conversion adapters on top of the primitive <code>Ulid</code> struct.</p><h2 id=\"architectural-overview%3A-moving-beyond-the-core-type\" tabindex=\"-1\">Architectural Overview: Moving Beyond the Core Type</h2><p>The <a href=\"https://byteaether.github.io/2026/streamlining-net-data-layers-with-byteaetherulid-v140/\">ByteAether.Ulid v1.4.0</a> release established native ORM and micro-ORM integrations across EF Core, LinqToDB, and Dapper. While this solved low-level database mapping challenges, application boundaries remained unoptimized.</p><p>Modern enterprise architectures expose two distinct integration boundaries:</p><ol><li><p><strong>HTTP/RPC API Edge</strong>: REST endpoints, Minimal APIs, and MVC controllers demand fast route constraint evaluation, efficient model binding, zero-allocation JSON parsing, and schema generation for OpenAPI definitions.</p></li><li><p><strong>Operational Tooling</strong>: DevOps pipelines, database migration scripts, and debugging sessions require CLI utilities to parse timestamps, extract randomness components, and convert byte representations without writing scratch C# applications.</p></li></ol><p><code>ByteAether.Ulid.AspNetCore</code> and <code>ByteAether.Ulid.Cli</code> address these requirements while preserving the foundational guarantees of the core <a href=\"https://github.com/ByteAether/Ulid\" target=\"_blank\" rel=\"noopener noreferrer\">ByteAether.Ulid repository</a>: zero-allocation parsing, high throughput, and monotonicity under bursty thread scheduling.</p><h2 id=\"source-first-asp.net-core-integration\" tabindex=\"-1\">Source-First ASP.NET Core Integration</h2><picture><source type=\"image/webp\" srcset=\"https://byteaether.github.io/2026/byteaetherulid-v142-expands-the-net-ecosystem-with-aspnet-core-and-cli-support/Js5oi21th6-384.webp 384w\"/><img loading=\"lazy\" decoding=\"async\" src=\"https://byteaether.github.io/2026/byteaetherulid-v142-expands-the-net-ecosystem-with-aspnet-core-and-cli-support/Js5oi21th6-384.png\" alt=\"ByteAether.Ulid.AspNetCore Library Logo\" style=\"float:right;max-width:30%;margin-left:2rem\" width=\"384\" height=\"384\"/></picture><p><a href=\"https://www.nuget.org/packages/ByteAether.Ulid.AspNetCore/\" target=\"_blank\" rel=\"noopener noreferrer\">ByteAether.Ulid.AspNetCore</a> introduces native framework bindings for ASP.NET Core pipelines:</p><ul><li><p><strong>Route Constraints (<code>:ulid</code>)</strong>: Evaluates incoming 26-character Base32 route parameters at the routing layer before triggering controller instantiation or delegate execution.</p></li><li><p><strong>System.Text.Json Serialization</strong>: Direct <code>JsonConverter&lt;Ulid&gt;</code> and <code>JsonConverter&lt;Ulid?&gt;</code> implementations handling canonical string representations without intermediate allocations.</p></li><li><p><strong>OpenAPI Integration</strong>: Schema transformers injecting the <code>string</code> format with a <code>ulid</code> designator into both Microsoft.AspNetCore.OpenApi and Swashbuckle metadata models.</p></li><li><p><strong>Native AOT &amp; Trim Compatibility</strong>: Fully compatible with source-generated JSON serializer contexts (<code>JsonSerializerContext</code>) required for Native AOT deployments.</p></li></ul><p>The following implementation demonstrates Minimal API endpoint integration with custom route validation:</p><pre class=\"language-csharp\"><code class=\"language-csharp\"><span class=\"token keyword\">using</span> <span class=\"token namespace\">ByteAether<span class=\"token punctuation\">.</span>Ulid</span><span class=\"token punctuation\">;</span>\n<span class=\"token keyword\">using</span> <span class=\"token namespace\">ByteAether<span class=\"token punctuation\">.</span>Ulid<span class=\"token punctuation\">.</span>AspNetCore</span><span class=\"token punctuation\">;</span>\n\n<span class=\"token class-name\"><span class=\"token keyword\">var</span></span> builder <span class=\"token operator\">=</span> WebApplication<span class=\"token punctuation\">.</span><span class=\"token function\">CreateBuilder</span><span class=\"token punctuation\">(</span>args<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\nbuilder<span class=\"token punctuation\">.</span>Services<span class=\"token punctuation\">.</span><span class=\"token function\">AddUlidRouteConstraint</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\n<span class=\"token class-name\"><span class=\"token keyword\">var</span></span> app <span class=\"token operator\">=</span> builder<span class=\"token punctuation\">.</span><span class=\"token function\">Build</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\napp<span class=\"token punctuation\">.</span><span class=\"token function\">MapGet</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"/users/{id:ulid}\"</span><span class=\"token punctuation\">,</span> <span class=\"token punctuation\">(</span><span class=\"token class-name\">Ulid</span> id<span class=\"token punctuation\">)</span> <span class=\"token operator\">=></span> Results<span class=\"token punctuation\">.</span><span class=\"token function\">Ok</span><span class=\"token punctuation\">(</span><span class=\"token keyword\">new</span> <span class=\"token constructor-invocation class-name\">UserResponse</span><span class=\"token punctuation\">(</span>id<span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\napp<span class=\"token punctuation\">.</span><span class=\"token function\">Run</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\n<span class=\"token keyword\">public</span> <span class=\"token keyword\">record</span> <span class=\"token class-name\">UserResponse</span><span class=\"token punctuation\">(</span><span class=\"token class-name\">Ulid</span> Id<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span></code></pre><p>By handling parsing and model binding at the routing boundary, the framework prevents malformed identifier requests from executing pipeline middleware or reaching application handlers.</p><p>For API spec generation, registering the schema transformer ensures client code generators emit strongly typed identifier fields instead of generic string definitions.</p><h2 id=\"operational-tooling%3A-cli-generation-and-inspection\" tabindex=\"-1\">Operational Tooling: CLI Generation and Inspection</h2><picture><source type=\"image/webp\" srcset=\"https://byteaether.github.io/2026/byteaetherulid-v142-expands-the-net-ecosystem-with-aspnet-core-and-cli-support/gLmaYmbscv-384.webp 384w\"/><img loading=\"lazy\" decoding=\"async\" src=\"https://byteaether.github.io/2026/byteaetherulid-v142-expands-the-net-ecosystem-with-aspnet-core-and-cli-support/gLmaYmbscv-384.png\" alt=\"ByteAether.Ulid.Cli Library Logo\" style=\"float:right;max-width:30%;margin-left:2rem\" width=\"384\" height=\"384\"/></picture><p><a href=\"https://www.nuget.org/packages/ByteAether.Ulid.Cli/\" target=\"_blank\" rel=\"noopener noreferrer\">ByteAether.Ulid.Cli</a> provides shell-level operations for inspecting and generating ULIDs.</p><p>Install the utility via the .NET SDK tool registry:</p><pre class=\"language-sh\"><code class=\"language-sh\">dotnet tool <span class=\"token function\">install</span> <span class=\"token parameter variable\">-g</span> ByteAether.Ulid.Cli</code></pre><h3 id=\"command-usage-and-scripting-capabilities\" tabindex=\"-1\">Command Usage and Scripting Capabilities</h3><p>Generate a single string identifier:</p><pre class=\"language-sh\"><code class=\"language-sh\">ulid</code></pre><p>Generate sequential identifiers in batches:</p><pre class=\"language-sh\"><code class=\"language-sh\">ulid <span class=\"token parameter variable\">--count</span> <span class=\"token number\">5</span></code></pre><p>Parse and inspect internal structure (extracting Unix timestamp and high-precision randomness payload):</p><pre class=\"language-sh\"><code class=\"language-sh\">ulid 01AN4Z07BY79KA1307SR9X4MV3</code></pre><p>Process standard input streams for log analysis or data transformation pipelines:</p><pre class=\"language-sh\"><code class=\"language-sh\"><span class=\"token function\">cat</span> ulids.txt <span class=\"token operator\">|</span> ulid <span class=\"token parameter variable\">--json</span></code></pre><h3 id=\"utility-features\" tabindex=\"-1\">Utility Features</h3><ul><li><p><strong>Format Interoperability</strong>: Converts canonical Base32 strings into raw Hexadecimal or <code>System.Guid</code> byte layouts.</p></li><li><p><strong>Deterministic Timestamp Overrides</strong>: Accepts custom unix epoch timestamps to reproduce state or generate test fixtures.</p></li><li><p><strong>Structured JSON Output</strong>: Emits machine-readable breakdown payloads for ingestion by tools like <code>jq</code> or powershell automation.</p></li></ul><h2 id=\"system-impact-and-adoption\" tabindex=\"-1\">System Impact and Adoption</h2><p>The expanded <a href=\"https://github.com/ByteAether/Ulid\" target=\"_blank\" rel=\"noopener noreferrer\">ByteAether.Ulid ecosystem</a> establishes a uniform model for identifier handling across the runtime lifecycle:</p><ol><li><p><strong>Performance Overhead</strong>: The core library remains a memory-efficient, zero-allocation struct implementation.</p></li><li><p><strong>Persistence Integrity</strong>: Dedicated ORM providers maintain raw byte-ordering efficiency at the database index layer.</p></li><li><p><strong>API Edge Precision</strong>: ASP.NET Core extensions validate and serialize inputs without custom application layer code.</p></li><li><p><strong>Developer Experience</strong>: Shell utilities standardise operational introspection and test vector generation.</p></li></ol><h3 id=\"package-installation\" tabindex=\"-1\">Package Installation</h3><p>Add the API dependencies or install the global shell utility:</p><pre class=\"language-sh\"><code class=\"language-sh\">dotnet <span class=\"token function\">add</span> package ByteAether.Ulid.AspNetCore\ndotnet tool <span class=\"token function\">install</span> <span class=\"token parameter variable\">-g</span> ByteAether.Ulid.Cli</code></pre><p>Reference material, core source files, and benchmark suites are accessible in the official <a href=\"https://github.com/ByteAether/Ulid\" target=\"_blank\" rel=\"noopener noreferrer\">ByteAether.Ulid GitHub repository</a>. Nuget packages are hosted on the public package index:</p><ul><li><p><a href=\"https://www.nuget.org/packages/ByteAether.Ulid.AspNetCore/\" target=\"_blank\" rel=\"noopener noreferrer\">ByteAether.Ulid.AspNetCore Package</a></p></li><li><p><a href=\"https://www.nuget.org/packages/ByteAether.Ulid.Cli/\" target=\"_blank\" rel=\"noopener noreferrer\">ByteAether.Ulid.Cli Package</a></p></li></ul>",
      "date_published": "2026-10-14T00:00:00Z"
    }
    ,
    {
      "id": "https://byteaether.github.io/2026/streamlining-net-data-layers-with-byteaetherulid-v140/",
      "url": "https://byteaether.github.io/2026/streamlining-net-data-layers-with-byteaetherulid-v140/",
      "title": "Streamlining .NET Data Layers with ByteAether.Ulid v1.4.0",
      "content_html": "<p>The choice of data identifiers dictates database performance, indexing efficiency, and security vectors at scale. For high-volume distributed systems, legacy options present clear operational drawbacks. Standard sequential integer identifiers expose internal operational velocity and collapse under cross-region replication due to centralized state bottlenecks. Traditional UUIDv4 variants eliminate the synchronization bottleneck but introduce severe index fragmentation within B-tree structures, degrading disk I/O throughput as tables scale.</p><p>While modern alternatives like UUIDv7 attempt to combine temporal sortability with randomness, they introduce distinct operational compromises. <a href=\"https://www.rfc-editor.org/rfc/rfc9562#name-monotonicity-and-counters\" target=\"_blank\" rel=\"noopener noreferrer\">The RFC 9562 standard</a> leaves sub-millisecond monotonicity optional. The native .NET Guid provider sacrifices strict ordering to prioritize generation speed. This approach reintroduces out-of-order database writes and causes index page splits under heavy execution loops.</p><p>The <a href=\"https://github.com/ulid/spec\" target=\"_blank\" rel=\"noopener noreferrer\">Universally Unique Lexicographically Sortable Identifier (ULID)</a> specification addresses these structural flaws by enforcing strict, deterministic monotonicity alongside lexicographical sortability. Earlier in our engineering roadmap, we introduced <a href=\"https://github.com/ByteAether/Ulid\" target=\"_blank\" rel=\"noopener noreferrer\">ByteAether.Ulid</a> to provide a zero-allocation, lock-free, and fully specification-compliant ULID engine for .NET.</p><p>However, an identifier primitive is only as valuable as its connection to the data access layer. Previously, integrating the ByteAether.Ulid primitive into object-relational mappers (ORMs) and micro-ORMs required substantial boilerplate, custom type handlers, and specialized value converters. Developers were forced to manually map internal structs to database engines, introducing risks such as misconfigured mappings, broken index optimizations, or mismatched endianness formats.</p><p>With the release of <a href=\"https://github.com/ByteAether/Ulid/releases/tag/v1.4.0\" target=\"_blank\" rel=\"noopener noreferrer\">ByteAether.Ulid v1.4.0</a>, we are moving past raw primitive mechanics. In addition to core performance enhancements, this version introduces a suite of official companion packages: <code>ByteAether.Ulid.EntityFrameworkCore</code>, <code>ByteAether.Ulid.linq2db</code>, and <code>ByteAether.Ulid.Dapper</code>. These extensions remove manual plumbing, allowing senior engineers and software architects to deploy high-performance ULIDs across the entire data access lifecycle with minimal configuration.</p><h3 id=\"a-new-visual-identity-for-our-libraries\" tabindex=\"-1\">A New Visual Identity for Our Libraries</h3><p>Along with this ecosystem expansion, we are introducing a subtle rebranding for our package logos. Previously, we used the identical blue, black, and white ByteAether identity across all NuGet packages, making it difficult to visually distinguish different libraries at a glance in long package lists.</p><p>While the ByteAether retains the original classic identity, we have updated our library logos to incorporate a distinct color-coding system. Each individual library we release now features a unique color assigned to the bottom-right quadrant of our four-segment logo. For companion integration libraries, we transition this segment using a smooth, dual-color gradient—blending from the primary library's color into the target framework's signature hue.</p><p>Other active packages in our ecosystem, such as our <code>WeakEvent</code> and <code>QueryLink</code> libraries, will receive this visual update treatment alongside their next releases.</p><div style=\"display:flex;justify-content:space-evenly\"><picture><source type=\"image/webp\" srcset=\"https://byteaether.github.io/2026/streamlining-net-data-layers-with-byteaetherulid-v140/XENPfiB4wl-384.webp 384w\"/><img loading=\"lazy\" decoding=\"async\" src=\"https://byteaether.github.io/2026/streamlining-net-data-layers-with-byteaetherulid-v140/XENPfiB4wl-384.png\" alt=\"ByteAether Logo\" style=\"width:25%\" width=\"384\" height=\"384\"/></picture><picture><source type=\"image/webp\" srcset=\"https://byteaether.github.io/2026/streamlining-net-data-layers-with-byteaetherulid-v140/9PfS4q2PYn-384.webp 384w\"/><img loading=\"lazy\" decoding=\"async\" src=\"https://byteaether.github.io/2026/streamlining-net-data-layers-with-byteaetherulid-v140/9PfS4q2PYn-384.png\" alt=\"ByteAether.Ulid Library Logo\" style=\"width:25%\" width=\"384\" height=\"384\"/></picture><picture><source type=\"image/webp\" srcset=\"https://byteaether.github.io/2026/streamlining-net-data-layers-with-byteaetherulid-v140/eoyV8aUJvQ-384.webp 384w\"/><img loading=\"lazy\" decoding=\"async\" src=\"https://byteaether.github.io/2026/streamlining-net-data-layers-with-byteaetherulid-v140/eoyV8aUJvQ-384.png\" alt=\"ByteAether.Ulid.EntityFrameworkCore Library Logo\" style=\"width:25%\" width=\"384\" height=\"384\"/></picture></div><h2 id=\"architectural-refresher%3A-the-mechanics-of-byteaether.ulid\" tabindex=\"-1\">Architectural Refresher: The Mechanics of ByteAether.Ulid</h2><p>To understand why direct data access integration is critical, we must look at the internal trade-offs managed by the core library. A ULID is a 128-bit structure composed of a 48-bit Unix timestamp (millisecond resolution) followed by an 80-bit random component. When encoded to text, it yields a 26-character string using <a href=\"https://www.crockford.com/base32.html\" target=\"_blank\" rel=\"noopener noreferrer\">Crockford's Base32 alphabet</a>. This ensures case-insensitive, URL-safe readability while maintaining perfect big-endian chronological sortability.</p><pre class=\"language-md\"><code class=\"language-md\"> 0                   1                   2                   3\n 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1\n+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n|                      32-bit Timestamp                         |\n+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n|     16-bit Timestamp          |       16-bit Random           |\n+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n|                      32-bit Random                            |\n+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n|                      32-bit Random                            |\n+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+</code></pre><h3 id=\"high-concurrency-and-monotonicity\" tabindex=\"-1\">High-Concurrency and Monotonicity</h3><p>When multiple identifiers are generated within the same millisecond, standard UUID engines or naive ULID implementations can suffer from two structural issues:</p><ol><li><strong>Random-Part Overflow</strong>: Under high-frequency allocation loops, incrementing the 80-bit random suffix sequentially to preserve monotonicity can saturate the random bits. This causes an unhandled <code>OverflowException</code>, halting transaction processing. While a naive mathematical analysis suggests this collision probability is a negligible 1 in 2<sup>80</sup>, the reality under high-concurrency workloads reveals greater volatility. As detailed in our previous analysis, <em><a href=\"https://byteaether.github.io/2025/prioritizing-reliability-when-milliseconds-arent-enough/\">&quot;Prioritizing Reliability When Milliseconds Aren't Enough&quot;</a></em>, the practical probability of encountering this overflow is significantly larger than intuition suggests.</li><li><strong>Enumeration Attack Vectors</strong>: Standard sequential increments create highly predictable tracking sequences, opening security gaps that allow bad actors to guess subsequent IDs.</li></ol><p>ByteAether.Ulid handles these edge cases directly. Our monotonic engine uses a lock-free Compare-and-Exchange (CAS) loop to prevent thread contention. If the 80-bit random space reaches saturation within a millisecond burst, the generator automatically increments the timestamp component. This approach prevents runtime errors and preserves strict chronological sorting under extreme load.</p><p>To mitigate enumeration risks, developers can inject multi-byte entropy steps using <code>GenerationOptions</code>:</p><pre class=\"language-csharp\"><code class=\"language-csharp\"><span class=\"token class-name\"><span class=\"token keyword\">var</span></span> options <span class=\"token operator\">=</span> <span class=\"token keyword\">new</span> <span class=\"token constructor-invocation class-name\">Ulid<span class=\"token punctuation\">.</span>GenerationOptions</span>\n<span class=\"token punctuation\">{</span>\n\tMonotonicity <span class=\"token operator\">=</span> MonotonicityOptions<span class=\"token punctuation\">.</span>MonotonicRandom2Byte<span class=\"token punctuation\">,</span>\n\tInitialRandomSource <span class=\"token operator\">=</span> <span class=\"token keyword\">new</span> <span class=\"token constructor-invocation class-name\">CryptographicallySecureRandomProvider</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">,</span>\n\tIncrementRandomSource <span class=\"token operator\">=</span> <span class=\"token keyword\">new</span> <span class=\"token constructor-invocation class-name\">PseudoRandomProvider</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span>\n<span class=\"token punctuation\">}</span><span class=\"token punctuation\">;</span>\n\n<span class=\"token class-name\">Ulid</span> secureId <span class=\"token operator\">=</span> Ulid<span class=\"token punctuation\">.</span><span class=\"token function\">New</span><span class=\"token punctuation\">(</span>options<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span></code></pre><p>By configuring <code>MonotonicRandom2Byte</code>, the engine generates a 16-bit pseudo-random (<code>IncrementRandomSource = new PseudoRandomProvider()</code>) integer and adds it plus one to the suffix, resulting in a delta ranging from 1 to 65,536 on consecutive generations within the same millisecond.</p><h3 id=\"low-level-performance-metrics\" tabindex=\"-1\">Low-Level Performance Metrics</h3><p>The core library relies on modern .NET memory primitives, explicit multi-targeting, and ahead-of-time (AOT) compilation compatibility to keep allocation overhead at zero. The metrics below represent a chosen subset of all benchmarks executed during development. The full, comprehensive test suite covering exhaustive operational states can be found directly in the GitHub repository at <a href=\"https://github.com/ByteAether/Ulid\" target=\"_blank\" rel=\"noopener noreferrer\">github.com/ByteAether/Ulid</a>. Please note that these results reflect performance figures current at the time of writing this article:</p><pre class=\"language-md\"><code class=\"language-md\"><span class=\"token table\"><span class=\"token table-header-row\"><span class=\"token punctuation\">|</span><span class=\"token table-header important\"> Type            </span><span class=\"token punctuation\">|</span><span class=\"token table-header important\"> Method             </span><span class=\"token punctuation\">|</span><span class=\"token table-header important\"> Mean        </span><span class=\"token punctuation\">|</span><span class=\"token table-header important\"> Gen0   </span><span class=\"token punctuation\">|</span><span class=\"token table-header important\"> Allocated </span><span class=\"token punctuation\">|</span>\n</span><span class=\"token table-line\"><span class=\"token punctuation\">|</span><span class=\"token punctuation\">----------------</span> <span class=\"token punctuation\">|</span><span class=\"token punctuation\">-------------------</span> <span class=\"token punctuation\">|</span><span class=\"token punctuation\">------------:</span><span class=\"token punctuation\">|</span><span class=\"token punctuation\">-------:</span><span class=\"token punctuation\">|</span><span class=\"token punctuation\">----------:</span><span class=\"token punctuation\">|</span>\n</span><span class=\"token table-data-rows\"><span class=\"token punctuation\">|</span><span class=\"token table-data\"> Generate        </span><span class=\"token punctuation\">|</span><span class=\"token table-data\"> ByteAetherUlid     </span><span class=\"token punctuation\">|</span><span class=\"token table-data\">  40.5719 ns </span><span class=\"token punctuation\">|</span><span class=\"token table-data\">      - </span><span class=\"token punctuation\">|</span><span class=\"token table-data\">         - </span><span class=\"token punctuation\">|</span>\n<span class=\"token punctuation\">|</span><span class=\"token table-data\"> Generate        </span><span class=\"token punctuation\">|</span><span class=\"token table-data\"> NetUlid            </span><span class=\"token punctuation\">|</span><span class=\"token table-data\"> 162.1996 ns </span><span class=\"token punctuation\">|</span><span class=\"token table-data\"> 0.0095 </span><span class=\"token punctuation\">|</span><span class=\"token table-data\">      80 B </span><span class=\"token punctuation\">|</span>\n<span class=\"token punctuation\">|</span><span class=\"token table-data\"> GenerateNonMono </span><span class=\"token punctuation\">|</span><span class=\"token table-data\"> ByteAetherUlidP    </span><span class=\"token punctuation\">|</span><span class=\"token table-data\">  40.9386 ns </span><span class=\"token punctuation\">|</span><span class=\"token table-data\">      - </span><span class=\"token punctuation\">|</span><span class=\"token table-data\">         - </span><span class=\"token punctuation\">|</span>\n<span class=\"token punctuation\">|</span><span class=\"token table-data\"> GenerateNonMono </span><span class=\"token punctuation\">|</span><span class=\"token table-data\"> GuidV7             </span><span class=\"token punctuation\">|</span><span class=\"token table-data\">  78.6568 ns </span><span class=\"token punctuation\">|</span><span class=\"token table-data\">      - </span><span class=\"token punctuation\">|</span><span class=\"token table-data\">         - </span><span class=\"token punctuation\">|</span>\n<span class=\"token punctuation\">|</span><span class=\"token table-data\"> FromString      </span><span class=\"token punctuation\">|</span><span class=\"token table-data\"> ByteAetherUlid     </span><span class=\"token punctuation\">|</span><span class=\"token table-data\">  15.2724 ns </span><span class=\"token punctuation\">|</span><span class=\"token table-data\">      - </span><span class=\"token punctuation\">|</span><span class=\"token table-data\">         - </span><span class=\"token punctuation\">|</span>\n<span class=\"token punctuation\">|</span><span class=\"token table-data\"> FromString      </span><span class=\"token punctuation\">|</span><span class=\"token table-data\"> NUlid              </span><span class=\"token punctuation\">|</span><span class=\"token table-data\">  64.0466 ns </span><span class=\"token punctuation\">|</span><span class=\"token table-data\"> 0.0086 </span><span class=\"token punctuation\">|</span><span class=\"token table-data\">      72 B </span><span class=\"token punctuation\">|</span>\n<span class=\"token punctuation\">|</span><span class=\"token table-data\"> CompareTo       </span><span class=\"token punctuation\">|</span><span class=\"token table-data\"> ByteAetherUlid     </span><span class=\"token punctuation\">|</span><span class=\"token table-data\">   1.3642 ns </span><span class=\"token punctuation\">|</span><span class=\"token table-data\">      - </span><span class=\"token punctuation\">|</span><span class=\"token table-data\">         - </span><span class=\"token punctuation\">|</span>\n<span class=\"token punctuation\">|</span><span class=\"token table-data\"> CompareTo       </span><span class=\"token punctuation\">|</span><span class=\"token table-data\"> Ulid (Cysharp)     </span><span class=\"token punctuation\">|</span><span class=\"token table-data\">   6.7843 ns </span><span class=\"token punctuation\">|</span><span class=\"token table-data\">      - </span><span class=\"token punctuation\">|</span><span class=\"token table-data\">         - </span><span class=\"token punctuation\">|</span>\n</span></span></code></pre><p>The engine achieves these sub-50 nanosecond generation speeds with zero managed heap allocations. This optimization prevents garbage collection pauses in hot paths like HTTP ingestion loops or message brokers.</p><h2 id=\"the-database-storage-challenge%3A-endianness-and-alignment\" tabindex=\"-1\">The Database Storage Challenge: Endianness and Alignment</h2><p>While memory-level optimizations provide significant benefits, they mean little if the identifier degrades database performance. Translating a 128-bit big-endian ULID structure into a relational database index requires careful planning around storage types and endianness alignment.</p><p>Relational database engines parse 128-bit identifiers differently depending on their internal page layouts:</p><ul><li><strong>String Formats (<code>CHAR(26)</code>)</strong>: Storing identifiers as Crockford's Base32 strings provides human readability and maintains global chronological sorting across all database backends. However, this comes at a steep cost: text fields require 26 bytes of storage space, which increases index size, reduces buffer pool page density, and slows down index scans.</li><li><strong>Raw Binary Formats (<code>BINARY(16)</code> / <code>BYTEA</code>)</strong>: Storing identifiers as 16-byte raw byte blocks offers maximum space efficiency. It retains big-endian byte order, matching the sorting rules of engines like PostgreSQL, MySQL (via <code>BINARY</code>), and SQLite.</li><li><strong>Native GUID Formats (<code>UUID</code> / <code>uniqueidentifier</code>)</strong>: This approach maps to native relational types, but introduces complexities depending on the provider. While engines like PostgreSQL handle .NET's mixed-endian <code>Guid</code> structure transparently, others (such as SQLite) store GUIDs as raw byte arrays, exposing the mixed-endian layout to the engine's sorting mechanisms and breaking chronological order.</li></ul><h3 id=\"the-microsoft-sql-server-uniqueidentifier-problem\" tabindex=\"-1\">The Microsoft SQL Server UniqueIdentifier Problem</h3><p>Microsoft SQL Server handles the <code>uniqueidentifier</code> type using an unusual internal sorting hierarchy. Instead of sorting a 16-byte block from left to right, its indexing engine evaluates bytes out of order. Specifically, it prioritizes the final 6 bytes (bytes 10 through 15) as the primary sorting criteria.</p><pre class=\"language-md\"><code class=\"language-md\">Standard Big-Endian ULID Layout:\n<span class=\"token url\">[<span class=\"token content\">0-5: Timestamp</span>] [<span class=\"token variable\">6-15: Monotonic Entropy</span>]</span>\n\nMicrosoft SQL Server uniqueidentifier Storage Target:\n<span class=\"token url\">[<span class=\"token content\">0-9: Random Padding / Sub-Entropy</span>] [<span class=\"token variable\">10-15: Most Significant Timestamp Bytes</span>]</span></code></pre><p>If a standard big-endian ULID is mapped directly to an MSSQL <code>uniqueidentifier</code> column without alteration, the database will sort the values using the random entropy block rather than the timestamp. This breaks chronological order, transforms sequential insertions into random writes, triggers heavy index page splits, and degrades I/O performance.</p><p>To prevent this issue, the companion libraries include <code>UlidStorageFormat.SqlServerGuid</code>. When selected, the integration library rearranges the internal bytes before sending them to the database driver. It shifts the 48-bit timestamp to the end of the byte block, ensuring that SQL Server's internal sorting logic aligns perfectly with the temporal order of generation.</p><h2 id=\"entity-framework-core-integration\" tabindex=\"-1\">Entity Framework Core Integration</h2><p>The <code>ByteAether.Ulid.EntityFrameworkCore</code> extension package simplifies type handling for Entity Framework Core architectures. Historically, using the library required developers to author and maintain their own custom value converters to bridge the <code>Ulid</code> primitive over to the database driver. While EF Core allowed applying those custom converters globally via convention hooks or explicitly per property, writing that boiler-plate mapping infrastructure fell entirely on the consumer. The companion package eliminates this operational friction by providing ready-made, highly optimized value converters out of the box alongside standard global registration hooks.</p><h3 id=\"installation\" tabindex=\"-1\">Installation</h3><pre class=\"language-sh\"><code class=\"language-sh\">dotnet <span class=\"token function\">add</span> package ByteAether.Ulid.EntityFrameworkCore</code></pre><h3 id=\"global-model-conventions\" tabindex=\"-1\">Global Model Conventions</h3><p>To apply ULID mappings uniformly across an entire domain model, override the <code>ConfigureConventions</code> hook inside the active <code>DbContext</code>. This applies the selected storage layout to all <code>Ulid</code> and <code>Nullable&lt;Ulid&gt;</code> properties discovered by the model scanner.</p><pre class=\"language-csharp\"><code class=\"language-csharp\"><span class=\"token keyword\">using</span> <span class=\"token namespace\">Microsoft<span class=\"token punctuation\">.</span>EntityFrameworkCore</span><span class=\"token punctuation\">;</span>\n<span class=\"token keyword\">using</span> <span class=\"token namespace\">ByteAether<span class=\"token punctuation\">.</span>Ulid<span class=\"token punctuation\">.</span>EntityFrameworkCore</span><span class=\"token punctuation\">;</span>\n\n<span class=\"token keyword\">public</span> <span class=\"token keyword\">sealed</span> <span class=\"token keyword\">class</span> <span class=\"token class-name\">ApplicationDbContext</span> <span class=\"token punctuation\">:</span> <span class=\"token type-list\"><span class=\"token class-name\">DbContext</span></span>\n<span class=\"token punctuation\">{</span>\n\t<span class=\"token keyword\">public</span> <span class=\"token return-type class-name\">DbSet<span class=\"token punctuation\">&lt;</span>Order<span class=\"token punctuation\">></span></span> Orders <span class=\"token operator\">=></span> <span class=\"token generic-method\"><span class=\"token function\">Set</span><span class=\"token generic class-name\"><span class=\"token punctuation\">&lt;</span>Order<span class=\"token punctuation\">></span></span></span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\t<span class=\"token keyword\">public</span> <span class=\"token return-type class-name\">DbSet<span class=\"token punctuation\">&lt;</span>Customer<span class=\"token punctuation\">></span></span> Customers <span class=\"token operator\">=></span> <span class=\"token generic-method\"><span class=\"token function\">Set</span><span class=\"token generic class-name\"><span class=\"token punctuation\">&lt;</span>Customer<span class=\"token punctuation\">></span></span></span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\n\t<span class=\"token keyword\">public</span> <span class=\"token function\">ApplicationDbContext</span><span class=\"token punctuation\">(</span><span class=\"token class-name\">DbContextOptions<span class=\"token punctuation\">&lt;</span>ApplicationDbContext<span class=\"token punctuation\">></span></span> options<span class=\"token punctuation\">)</span>\n\t\t<span class=\"token punctuation\">:</span> <span class=\"token keyword\">base</span><span class=\"token punctuation\">(</span>options<span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span> <span class=\"token punctuation\">}</span>\n\n\t<span class=\"token keyword\">protected</span> <span class=\"token keyword\">override</span> <span class=\"token return-type class-name\"><span class=\"token keyword\">void</span></span> <span class=\"token function\">ConfigureConventions</span><span class=\"token punctuation\">(</span><span class=\"token class-name\">ModelConfigurationBuilder</span> configurationBuilder<span class=\"token punctuation\">)</span>\n\t<span class=\"token punctuation\">{</span>\n\t\t<span class=\"token comment\">// Registers global mappings for both Ulid and Ulid? types.</span>\n\t\t<span class=\"token comment\">// Available strategies: String, Binary, Guid, and SqlServerGuid</span>\n\t\tconfigurationBuilder<span class=\"token punctuation\">.</span><span class=\"token function\">RegisterUlid</span><span class=\"token punctuation\">(</span>UlidStorageFormat<span class=\"token punctuation\">.</span>Binary<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\t<span class=\"token punctuation\">}</span>\n<span class=\"token punctuation\">}</span></code></pre><h3 id=\"fine-grained-property-mapping\" tabindex=\"-1\">Fine-Grained Property Mapping</h3><p>If an application works with multiple legacy storage schemas, global conventions can be overridden. By using fluent API declarations inside <code>OnModelCreating</code>, architects can configure distinct storage strategies for specific tables or columns.</p><pre class=\"language-csharp\"><code class=\"language-csharp\"><span class=\"token keyword\">protected</span> <span class=\"token keyword\">override</span> <span class=\"token return-type class-name\"><span class=\"token keyword\">void</span></span> <span class=\"token function\">OnModelCreating</span><span class=\"token punctuation\">(</span><span class=\"token class-name\">ModelBuilder</span> modelBuilder<span class=\"token punctuation\">)</span>\n<span class=\"token punctuation\">{</span>\n\t<span class=\"token keyword\">base</span><span class=\"token punctuation\">.</span><span class=\"token function\">OnModelCreating</span><span class=\"token punctuation\">(</span>modelBuilder<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\n\tmodelBuilder<span class=\"token punctuation\">.</span><span class=\"token generic-method\"><span class=\"token function\">Entity</span><span class=\"token generic class-name\"><span class=\"token punctuation\">&lt;</span>Customer<span class=\"token punctuation\">></span></span></span><span class=\"token punctuation\">(</span>entity <span class=\"token operator\">=></span>\n\t<span class=\"token punctuation\">{</span>\n\t\t<span class=\"token comment\">// Persist as a 26-character human-readable string</span>\n\t\tentity<span class=\"token punctuation\">.</span><span class=\"token function\">Property</span><span class=\"token punctuation\">(</span>c <span class=\"token operator\">=></span> c<span class=\"token punctuation\">.</span>Id<span class=\"token punctuation\">)</span>\n\t\t\t  <span class=\"token punctuation\">.</span><span class=\"token generic-method\"><span class=\"token function\">HasConversion</span><span class=\"token generic class-name\"><span class=\"token punctuation\">&lt;</span>UlidToStringConverter<span class=\"token punctuation\">></span></span></span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\t<span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\n\tmodelBuilder<span class=\"token punctuation\">.</span><span class=\"token generic-method\"><span class=\"token function\">Entity</span><span class=\"token generic class-name\"><span class=\"token punctuation\">&lt;</span>Order<span class=\"token punctuation\">></span></span></span><span class=\"token punctuation\">(</span>entity <span class=\"token operator\">=></span>\n\t<span class=\"token punctuation\">{</span>\n\t\t<span class=\"token comment\">// Persist as a standard native GUID block</span>\n\t\tentity<span class=\"token punctuation\">.</span><span class=\"token function\">Property</span><span class=\"token punctuation\">(</span>o <span class=\"token operator\">=></span> o<span class=\"token punctuation\">.</span>Id<span class=\"token punctuation\">)</span>\n\t\t\t  <span class=\"token punctuation\">.</span><span class=\"token generic-method\"><span class=\"token function\">HasConversion</span><span class=\"token generic class-name\"><span class=\"token punctuation\">&lt;</span>UlidToGuidConverter<span class=\"token punctuation\">></span></span></span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\t<span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\n\tmodelBuilder<span class=\"token punctuation\">.</span><span class=\"token generic-method\"><span class=\"token function\">Entity</span><span class=\"token generic class-name\"><span class=\"token punctuation\">&lt;</span>ArchiveLog<span class=\"token punctuation\">></span></span></span><span class=\"token punctuation\">(</span>entity <span class=\"token operator\">=></span>\n\t<span class=\"token punctuation\">{</span>\n\t\t<span class=\"token comment\">// Re-order byte arrays specifically for SQL Server uniqueidentifier indexes</span>\n\t\tentity<span class=\"token punctuation\">.</span><span class=\"token function\">Property</span><span class=\"token punctuation\">(</span>a <span class=\"token operator\">=></span> a<span class=\"token punctuation\">.</span>Id<span class=\"token punctuation\">)</span>\n\t\t\t  <span class=\"token punctuation\">.</span><span class=\"token generic-method\"><span class=\"token function\">HasConversion</span><span class=\"token generic class-name\"><span class=\"token punctuation\">&lt;</span>UlidToSqlServerGuidConverter<span class=\"token punctuation\">></span></span></span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\t<span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span></code></pre><h2 id=\"linqtodb-micro-orm-integration\" tabindex=\"-1\">LinqToDB Micro-ORM Integration</h2><p>For data layers built on the LinqToDB micro-ORM — mirroring the architectural layout explored in our <em><a href=\"https://byteaether.github.io/series/enterprise-dal/\">Building an Enterprise Data Access Layer</a></em> series — the <code>ByteAether.Ulid.linq2db</code> package introduces a streamlined integration route. While our original enterprise series relied on localized, embedded code snippets to coordinate LinqToDB data types prior to the v1.4.0 release, this extension replaces that manual plumbing by hooking directly into the <code>DataOptions</code> pipeline via a single, standardized configuration call.</p><h3 id=\"installation-1\" tabindex=\"-1\">Installation</h3><pre class=\"language-sh\"><code class=\"language-sh\">dotnet <span class=\"token function\">add</span> package ByteAether.Ulid.linq2db</code></pre><h3 id=\"configuration-pipeline\" tabindex=\"-1\">Configuration Pipeline</h3><p>Register the ULID conventions directly during the initialization of the <code>DataOptions</code> instance. This step configures the mapping schema before opening any database connections.</p><pre class=\"language-csharp\"><code class=\"language-csharp\"><span class=\"token keyword\">using</span> <span class=\"token namespace\">LinqToDB</span><span class=\"token punctuation\">;</span>\n<span class=\"token keyword\">using</span> <span class=\"token namespace\">ByteAether<span class=\"token punctuation\">.</span>Ulid<span class=\"token punctuation\">.</span>LinqToDB</span><span class=\"token punctuation\">;</span>\n\n<span class=\"token keyword\">public</span> <span class=\"token keyword\">static</span> <span class=\"token keyword\">class</span> <span class=\"token class-name\">LinqToDbConfiguration</span>\n<span class=\"token punctuation\">{</span>\n\t<span class=\"token keyword\">public</span> <span class=\"token keyword\">static</span> <span class=\"token return-type class-name\">DataOptions</span> <span class=\"token function\">BuildOptions</span><span class=\"token punctuation\">(</span><span class=\"token class-name\"><span class=\"token keyword\">string</span></span> connectionString<span class=\"token punctuation\">)</span>\n\t<span class=\"token punctuation\">{</span>\n\t\t<span class=\"token keyword\">return</span> <span class=\"token keyword\">new</span> <span class=\"token constructor-invocation class-name\">DataOptions</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span>\n\t\t\t<span class=\"token punctuation\">.</span><span class=\"token function\">UseSQLite</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span>\n\t\t\t<span class=\"token punctuation\">.</span><span class=\"token function\">UseConnectionString</span><span class=\"token punctuation\">(</span>connectionString<span class=\"token punctuation\">)</span>\n\t\t\t<span class=\"token comment\">// Seamlessly registers global mapping engines for Ulid and Ulid?</span>\n\t\t\t<span class=\"token punctuation\">.</span><span class=\"token function\">RegisterUlid</span><span class=\"token punctuation\">(</span>UlidStorageFormat<span class=\"token punctuation\">.</span>Binary<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\t<span class=\"token punctuation\">}</span>\n<span class=\"token punctuation\">}</span></code></pre><p>Once registered, standard LINQ expressions translate <code>Ulid</code> properties directly into parameterized SQL statements without requiring manual casting or intermediate transformations.</p><h2 id=\"dapper-integration\" tabindex=\"-1\">Dapper Integration</h2><p>Dapper maps .NET types globally using a strict one-to-one relationship (<code>Type</code> -&gt; <code>TypeHandler</code>). The <code>ByteAether.Ulid.Dapper</code> companion package hooks into Dapper's internal type handler cache to ensure clean, automated parameter serialization.</p><h3 id=\"installation-2\" tabindex=\"-1\">Installation</h3><pre class=\"language-sh\"><code class=\"language-sh\">dotnet <span class=\"token function\">add</span> package ByteAether.Ulid.Dapper</code></pre><h3 id=\"initialization\" tabindex=\"-1\">Initialization</h3><p>Invoke <code>DapperUlid.RegisterUlid()</code> once during application startup (typically within <code>Program.cs</code>), prior to executing any database interactions.</p><pre class=\"language-csharp\"><code class=\"language-csharp\"><span class=\"token keyword\">using</span> <span class=\"token namespace\">ByteAether<span class=\"token punctuation\">.</span>Ulid<span class=\"token punctuation\">.</span>Dapper</span><span class=\"token punctuation\">;</span>\n\n<span class=\"token keyword\">public</span> <span class=\"token keyword\">static</span> <span class=\"token keyword\">class</span> <span class=\"token class-name\">Program</span>\n<span class=\"token punctuation\">{</span>\n\t<span class=\"token keyword\">public</span> <span class=\"token keyword\">static</span> <span class=\"token return-type class-name\"><span class=\"token keyword\">void</span></span> <span class=\"token function\">Main</span><span class=\"token punctuation\">(</span><span class=\"token class-name\"><span class=\"token keyword\">string</span><span class=\"token punctuation\">[</span><span class=\"token punctuation\">]</span></span> args<span class=\"token punctuation\">)</span>\n\t<span class=\"token punctuation\">{</span>\n\t\t<span class=\"token comment\">// Apply the chosen binary storage mapping globally across Dapper operations</span>\n\t\tDapperUlid<span class=\"token punctuation\">.</span><span class=\"token function\">RegisterUlid</span><span class=\"token punctuation\">(</span>UlidStorageFormat<span class=\"token punctuation\">.</span>Binary<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\n\t\t<span class=\"token comment\">// Continue running standard host infrastructure...</span>\n\t<span class=\"token punctuation\">}</span>\n<span class=\"token punctuation\">}</span></code></pre><h3 id=\"executing-queries\" tabindex=\"-1\">Executing Queries</h3><p>With the type handler registered, Dapper automatically processes <code>Ulid</code> fields during parameter execution and object mapping.</p><pre class=\"language-csharp\"><code class=\"language-csharp\"><span class=\"token keyword\">using</span> <span class=\"token namespace\">Dapper</span><span class=\"token punctuation\">;</span>\n<span class=\"token keyword\">using</span> <span class=\"token namespace\">System<span class=\"token punctuation\">.</span>Data</span><span class=\"token punctuation\">;</span>\n\n<span class=\"token keyword\">public</span> <span class=\"token keyword\">sealed</span> <span class=\"token keyword\">class</span> <span class=\"token class-name\">AccountRepository</span>\n<span class=\"token punctuation\">{</span>\n\t<span class=\"token keyword\">private</span> <span class=\"token keyword\">readonly</span> <span class=\"token class-name\">IDbConnection</span> _dbConnection<span class=\"token punctuation\">;</span>\n\n\t<span class=\"token keyword\">public</span> <span class=\"token function\">AccountRepository</span><span class=\"token punctuation\">(</span><span class=\"token class-name\">IDbConnection</span> dbConnection<span class=\"token punctuation\">)</span> <span class=\"token operator\">=></span> _dbConnection <span class=\"token operator\">=</span> dbConnection<span class=\"token punctuation\">;</span>\n\n\t<span class=\"token keyword\">public</span> <span class=\"token keyword\">async</span> <span class=\"token return-type class-name\">Task<span class=\"token punctuation\">&lt;</span>Account<span class=\"token punctuation\">?</span><span class=\"token punctuation\">></span></span> <span class=\"token function\">GetByIdAsync</span><span class=\"token punctuation\">(</span><span class=\"token class-name\">Ulid</span> accountId<span class=\"token punctuation\">)</span>\n\t<span class=\"token punctuation\">{</span>\n\t\t<span class=\"token keyword\">const</span> <span class=\"token class-name\"><span class=\"token keyword\">string</span></span> sql <span class=\"token operator\">=</span> <span class=\"token string\">\"SELECT * FROM Accounts WHERE Id = @Id\"</span><span class=\"token punctuation\">;</span>\n\n\t\t<span class=\"token comment\">// The accountId argument is automatically serialized into a 16-byte block</span>\n\t\t<span class=\"token keyword\">return</span> <span class=\"token keyword\">await</span> _dbConnection<span class=\"token punctuation\">.</span><span class=\"token generic-method\"><span class=\"token function\">QueryFirstOrDefaultAsync</span><span class=\"token generic class-name\"><span class=\"token punctuation\">&lt;</span>Account<span class=\"token punctuation\">></span></span></span><span class=\"token punctuation\">(</span>sql<span class=\"token punctuation\">,</span> <span class=\"token keyword\">new</span> <span class=\"token punctuation\">{</span> Id <span class=\"token operator\">=</span> accountId <span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\t<span class=\"token punctuation\">}</span>\n<span class=\"token punctuation\">}</span></code></pre><p><em>Note: Because Dapper uses a global type handler registration system, applications must standardize on a single storage format per execution lifecycle.</em></p><h2 id=\"high-performance-time-range-filtering\" tabindex=\"-1\">High-Performance Time-Range Filtering</h2><p>A major advantage of using ULIDs over traditional UUIDs is the embedded 48-bit millisecond timestamp. This allows developers to run fast time-range queries directly against the primary identifier index, eliminating the need for a secondary index on a <code>CreatedAt</code> column.</p><p>Using <code>Ulid.MinAt()</code> and <code>Ulid.MaxAt()</code>, applications can generate boundary identifiers for precise time windows.</p><pre class=\"language-csharp\"><code class=\"language-csharp\"><span class=\"token keyword\">public</span> <span class=\"token keyword\">async</span> <span class=\"token return-type class-name\">Task<span class=\"token punctuation\">&lt;</span>IReadOnlyList<span class=\"token punctuation\">&lt;</span>Order<span class=\"token punctuation\">></span><span class=\"token punctuation\">></span></span> <span class=\"token function\">GetOrdersFromPastWeekAsync</span><span class=\"token punctuation\">(</span><span class=\"token class-name\">ApplicationDbContext</span> context<span class=\"token punctuation\">)</span>\n<span class=\"token punctuation\">{</span>\n\t<span class=\"token class-name\">DateTimeOffset</span> startBoundary <span class=\"token operator\">=</span> DateTimeOffset<span class=\"token punctuation\">.</span>UtcNow<span class=\"token punctuation\">.</span><span class=\"token function\">AddDays</span><span class=\"token punctuation\">(</span><span class=\"token operator\">-</span><span class=\"token number\">7</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\t<span class=\"token class-name\">DateTimeOffset</span> endBoundary <span class=\"token operator\">=</span> DateTimeOffset<span class=\"token punctuation\">.</span>UtcNow<span class=\"token punctuation\">;</span>\n\n\t<span class=\"token comment\">// Generate boundary tokens matching the precise timestamps</span>\n\t<span class=\"token class-name\">Ulid</span> minId <span class=\"token operator\">=</span> Ulid<span class=\"token punctuation\">.</span><span class=\"token function\">MinAt</span><span class=\"token punctuation\">(</span>startBoundary<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\t<span class=\"token class-name\">Ulid</span> maxId <span class=\"token operator\">=</span> Ulid<span class=\"token punctuation\">.</span><span class=\"token function\">MaxAt</span><span class=\"token punctuation\">(</span>endBoundary<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\n\t<span class=\"token comment\">// Queries translate to standard, highly optimized index scans:</span>\n\t<span class=\"token comment\">// WHERE Id >= @minId AND Id &lt;= @maxId</span>\n\t<span class=\"token keyword\">return</span> <span class=\"token keyword\">await</span> context<span class=\"token punctuation\">.</span>Orders\n\t\t<span class=\"token punctuation\">.</span><span class=\"token function\">Where</span><span class=\"token punctuation\">(</span>order <span class=\"token operator\">=></span> order<span class=\"token punctuation\">.</span>Id <span class=\"token operator\">>=</span> minId <span class=\"token operator\">&amp;&amp;</span> order<span class=\"token punctuation\">.</span>Id <span class=\"token operator\">&lt;=</span> maxId<span class=\"token punctuation\">)</span>\n\t\t<span class=\"token punctuation\">.</span><span class=\"token function\">ToListAsync</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span></code></pre><h3 id=\"essential-production-requirements\" tabindex=\"-1\">Essential Production Requirements</h3><p>When running time-range queries against database tables, keep these architectural requirements in mind:</p><ol><li><strong>Storage Format Consistency</strong>: The storage strategy must preserve lexicographical sortability. <code>UlidStorageFormat.Binary</code> and <code>UlidStorageFormat.String</code> maintain correct chronological sorting across all platforms.</li><li><strong>Byte Order Mismatches &amp; Engine Behavior</strong>: The success of range queries depends heavily on how a database engine and its ADO.NET provider manage GUID layouts. For instance, PostgreSQL's native <code>uuid</code> type handles .NET's <code>Guid</code> cleanly, allowing range queries to resolve correctly. Conversely, engines like SQLite typically persist GUID values as raw byte blocks, which causes immediate sorting and ordering issues due to the conflict between .NET's mixed-endian internal <code>Guid</code> representation and the database's expected sort behavior. Before deploying <code>UlidStorageFormat.Guid</code>, thoroughly verify how your specific provider maps and indexes the underlying bytes.</li><li><strong>SQL Server Index Routing</strong>: When working with Microsoft SQL Server, always use the <code>SqlServerGuid</code> storage format. This ensures that the generated values match SQL Server's internal <code>uniqueidentifier</code> indexing rules, allowing for fast, efficient index scans.</li></ol><h2 id=\"conclusion\" tabindex=\"-1\">Conclusion</h2><p>The release of ByteAether.Ulid v1.4.0 shifts the library's focus from a standalone high-performance primitive to a fully integrated ecosystem. By introducing official packages for <a href=\"https://www.nuget.org/packages/ByteAether.Ulid.EntityFrameworkCore\" target=\"_blank\" rel=\"noopener noreferrer\">EF Core</a>, <a href=\"https://www.nuget.org/packages/ByteAether.Ulid.linq2db\" target=\"_blank\" rel=\"noopener noreferrer\">LinqToDB</a>, and <a href=\"https://www.nuget.org/packages/ByteAether.Ulid.Dapper\" target=\"_blank\" rel=\"noopener noreferrer\">Dapper</a>, it eliminates the manual boilerplate and database alignment issues that previously complicated production deployments.</p><p>Whether you are designing a high-throughput microservices architecture or managing heavy enterprise data models, these companion libraries handle the low-level serialization details for you. They ensure that your application benefits from zero-allocation, thread-safe monotonic identifiers while keeping your database indexes lean and highly performant.</p><p>For source code, complete benchmark datasets, and detailed documentation, visit the official repository at <a href=\"https://github.com/ByteAether/Ulid\" target=\"_blank\" rel=\"noopener noreferrer\">github.com/ByteAether/Ulid</a>.</p>",
      "date_published": "2026-07-22T00:00:00Z"
    }
    ,
    {
      "id": "https://byteaether.github.io/2026/the-architecture-of-obsession-why-byteaetherulid-is-the-last-identifier-library-you-will-ever-need/",
      "url": "https://byteaether.github.io/2026/the-architecture-of-obsession-why-byteaetherulid-is-the-last-identifier-library-you-will-ever-need/",
      "title": "The Architecture of Obsession: Why ByteAether.Ulid is the Last Identifier Library You Will Ever Need",
      "content_html": "<p>Most developers treat unique identifiers like plumbing. You want them to work, you want them to be out of sight, and you certainly do not want to spend your Friday nights thinking about them. For the average CRUD application, <code>Guid.NewGuid()</code> is a perfectly acceptable choice. However, if you are reading this, you are likely not an average developer. You are an architect or a senior engineer who understands that at scale, &quot;good enough&quot; is a ticking time bomb of index fragmentation and out-of-order writes.</p><p>I have spent an unreasonable amount of time obsessing over <a href=\"https://github.com/ulid/spec\" target=\"_blank\" rel=\"noopener noreferrer\">Universally Unique Lexicographically Sortable Identifiers (ULIDs)</a>. While the rest of the world moved on to other things, I stayed behind to build <strong><a href=\"https://github.com/ByteAether/Ulid\" target=\"_blank\" rel=\"noopener noreferrer\">ByteAether.Ulid</a></strong>, a library that is, quite frankly, over-engineered to the point of insanity. But in a world of &quot;lazy&quot; implementations and optional monotonicity, perhaps a bit of insanity is exactly what your distributed system needs.</p><h2 id=\"the-sortability%3A-why-uuidv7-isn't-the-hero-we-expected\" tabindex=\"-1\">The Sortability: Why UUIDv7 Isn't the Hero We Expected</h2><p>For a long time, the industry waited for a standardized, sortable replacement for the legacy GUID (UUIDv4). When UUIDv7 arrived via <a href=\"https://www.rfc-editor.org/rfc/rfc9562#name-monotonicity-and-counters\" target=\"_blank\" rel=\"noopener noreferrer\">RFC 9562</a>, it seemed like the answer. Even .NET introduced a native provider for it. However, if you dig into the implementation details (specifically how the <a href=\"https://github.com/dotnet/runtime/blob/571b044582ceb7fe426b7f143c703064aa9ea4db/src/libraries/System.Private.CoreLib/src/System/Guid.cs#L306\" target=\"_blank\" rel=\"noopener noreferrer\">native .NET provider implements the generation of UUIDv7</a>), you find a compromise.</p><p>The specification for UUIDv7 makes monotonicity <strong>optional</strong>. This means that during a high-concurrency burst within the same millisecond, the native provider may sacrifice strict ordering to maintain performance. To a database architect, this is heresy. Out-of-order identifiers lead to page splits in B-Tree indexes, which leads to physical disk fragmentation and degraded insert performance.</p><p>ULIDs solve this by design. By mandating lexicographical sortability and strict monotonic increments, ULID ensures that your primary keys are always appended to the end of the index. ByteAether.Ulid doubles down on this by providing a robust, fully compliant .NET implementation that refuses to take the &quot;lazy&quot; path.</p><h2 id=\"eliminating-the-overflow-exception\" tabindex=\"-1\">Eliminating the Overflow Exception</h2><p>One of the <a href=\"https://github.com/ulid/spec?tab=readme-ov-file#monotonicity\" target=\"_blank\" rel=\"noopener noreferrer\">darker corners of the ULID specification</a> involves what happens when the 80-bit random component overflows during a monotonic increment within the same millisecond. Many libraries simply throw an <code>OverflowException</code> (as specification dictates), effectively crashing your ingestion pipeline because you were &quot;too fast&quot; for the generator. This isn't just a theoretical concern for high-volume systems as the initial random component is cryptographically generated and it could start near its maximum value, causing the very next increment to fail.</p><p>In <a href=\"https://github.com/ByteAether/Ulid\" target=\"_blank\" rel=\"noopener noreferrer\">ByteAether.Ulid</a>, we have implemented a more elegant solution. When the random part overflows, the library automatically increments the timestamp component. This ensures that unique ULID generation continues without interruption while maintaining the sort order. This behavior ensures that your system remains resilient even under extreme throughput. For a deep dive into the engineering rationale and why this is more likely than you think, check out our dedicated article on <a href=\"https://byteaether.github.io/2025/prioritizing-reliability-when-milliseconds-arent-enough/\">Handling ULID Overflows Gracefully</a>.</p><h2 id=\"high-performance%2C-lock-free-concurrency\" tabindex=\"-1\">High-Performance, Lock-Free Concurrency</h2><p>In high-concurrency environments, locks are the enemy of throughput. If every thread in your web farm has to wait for a global lock just to generate an ID, you have created a bottleneck.</p><p>ByteAether.Ulid utilizes a <strong>lock-free compare-and-exchange (CAS)</strong> approach for monotonic generation. This allows multiple threads to attempt to increment the state simultaneously without traditional locking overhead. If a collision occurs, the CAS operation retries until it succeeds, ensuring thread safety with maximum performance.</p><p>In terms of raw speed, ByteAether.Ulid stands as one of the most performant implementations available, often matching or exceeding the most optimized competitors in the .NET ecosystem while maintaining a zero-allocation footprint for standard generation. Detailed performance metrics and environment-specific results are maintained on our <a href=\"https://github.com/ByteAether/Ulid?tab=readme-ov-file#benchmarking\" target=\"_blank\" rel=\"noopener noreferrer\">GitHub page</a>.</p><h2 id=\"preventing-enumeration-attacks-with-random-increments\" tabindex=\"-1\">Preventing Enumeration Attacks with Random Increments</h2><p>Standard monotonic ULIDs can be predictable. If an attacker knows one ULID, they can often guess the next one by simply adding 1 to the random component. This is <a href=\"https://github.com/ulid/spec/issues/105\" target=\"_blank\" rel=\"noopener noreferrer\">a significant security risk</a> for public-facing APIs.</p><p>To mitigate this, I have implemented configurable random increments. Instead of adding a static <code>1</code> to the random component, you can configure the library to use a random increment ranging from 1 byte up to 4 bytes. This significantly increases the entropy between sequential IDs, making enumeration attacks computationally expensive while still preserving the lexicographical sortability required for your database indexes.</p><h2 id=\"the-developer-experience%3A-frictionless-integration\" tabindex=\"-1\">The Developer Experience: Frictionless Integration</h2><p>A library should feel like a native part of your stack, not an obstacle. I've focused on ensuring that <strong>ByteAether.Ulid</strong> integrates seamlessly with modern .NET defaults while providing clear pathways for specialized tooling. The library includes native, built-in support for:</p><ul><li><strong>System.Text.Json:</strong> Includes a high-performance <code>JsonConverter</code> for effortless serialization to canonical Base32 strings.</li><li><strong>ASP.NET Core:</strong> Built-in <code>TypeConverter</code> support allows you to use <code>Ulid</code> directly as a route or query parameter in your controllers and Minimal APIs.</li></ul><p>Beyond the standard runtime bindings, the integration model balances zero-dependency core architecture with ecosystem convenience:</p><ul><li><strong>Dedicated Companion Libraries</strong>: For primary ORM and data-access frameworks, official companion packages are available on NuGet, including <a href=\"https://www.nuget.org/packages/ByteAether.Ulid.EntityFrameworkCore/\" target=\"_blank\" rel=\"noopener noreferrer\"><code>ByteAether.Ulid.EntityFrameworkCore</code></a>, <a href=\"https://www.nuget.org/packages/ByteAether.Ulid.linq2db/\" target=\"_blank\" rel=\"noopener noreferrer\"><code>ByteAether.Ulid.linq2db</code></a>, and <a href=\"https://www.nuget.org/packages/ByteAether.Ulid.Dapper/\" target=\"_blank\" rel=\"noopener noreferrer\"><code>ByteAether.Ulid.Dapper</code></a>. These libraries encapsulate type conversion, query translator integration, and provider-specific storage behaviors (such as optimized SQL Server GUID layouts) out of the box.</li><li><strong>Snippet-Based Integrations</strong>: For serialization or protocol tools where a dedicated package dependency is unnecessary (such as <strong>Newtonsoft.Json</strong> or <strong>MessagePack</strong>) the repository provides minimal, zero-allocation converter snippets in the <a href=\"https://github.com/ByteAether/Ulid?tab=readme-ov-file#-integration-with-other-libraries\" target=\"_blank\" rel=\"noopener noreferrer\">GitHub README</a> that can be dropped directly into your codebase.</li></ul><h3 id=\"advanced-filtering-with-linq\" tabindex=\"-1\">Advanced Filtering with LINQ</h3><p>One of the most powerful features of ULIDs is the ability to perform time-range queries without a separate <code>CreatedAt</code> column. Because the timestamp is embedded in the ID, you can generate boundary ULIDs:</p><pre class=\"language-csharp\"><code class=\"language-csharp\"><span class=\"token class-name\"><span class=\"token keyword\">var</span></span> start <span class=\"token operator\">=</span> Ulid<span class=\"token punctuation\">.</span><span class=\"token function\">MinAt</span><span class=\"token punctuation\">(</span>DateTimeOffset<span class=\"token punctuation\">.</span>UtcNow<span class=\"token punctuation\">.</span><span class=\"token function\">AddDays</span><span class=\"token punctuation\">(</span><span class=\"token operator\">-</span><span class=\"token number\">7</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token class-name\"><span class=\"token keyword\">var</span></span> end <span class=\"token operator\">=</span> Ulid<span class=\"token punctuation\">.</span><span class=\"token function\">MaxAt</span><span class=\"token punctuation\">(</span>DateTimeOffset<span class=\"token punctuation\">.</span>UtcNow<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\n<span class=\"token comment\">// EF Core translates this into an efficient range comparison on the Primary Key</span>\n<span class=\"token class-name\"><span class=\"token keyword\">var</span></span> reports <span class=\"token operator\">=</span> <span class=\"token keyword\">await</span> context<span class=\"token punctuation\">.</span>Reports\n\t<span class=\"token punctuation\">.</span><span class=\"token function\">Where</span><span class=\"token punctuation\">(</span>r <span class=\"token operator\">=></span> r<span class=\"token punctuation\">.</span>Id <span class=\"token operator\">>=</span> start <span class=\"token operator\">&amp;&amp;</span> r<span class=\"token punctuation\">.</span>Id <span class=\"token operator\">&lt;=</span> end<span class=\"token punctuation\">)</span>\n\t<span class=\"token punctuation\">.</span><span class=\"token function\">ToListAsync</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span></code></pre><p>This approach allows you to leverage the primary key index for high-performance temporal queries, reducing the need for additional composite indexes.</p><h2 id=\"embracing-the-over-engineered\" tabindex=\"-1\">Embracing the Over-Engineered</h2><p>The development of ByteAether.Ulid represents a commitment to refining edge cases, ensuring AOT (Ahead-of-Time) compilation compatibility, and hardening logic against the weirdest distributed system failures.</p><p>Is it overkill? Probably. Do you need a library that handles sub-millisecond random overflows via timestamp carry-over using lock-free CAS? Maybe not today. But when your system scales to millions of events per second and your database starts choking on index fragmentation, you will be glad someone was &quot;insane&quot; enough to build this.</p><p>Stop settling for &quot;lazy&quot; identifiers. Give your architecture the precision it deserves by checking out the project on <a href=\"https://github.com/ByteAether/Ulid\" target=\"_blank\" rel=\"noopener noreferrer\">GitHub</a>.</p>",
      "date_published": "2026-04-02T00:00:00Z"
    }
    ,
    {
      "id": "https://byteaether.github.io/2026/the-weight-of-decisions-solving-weighted-random-sorting-at-scale/",
      "url": "https://byteaether.github.io/2026/the-weight-of-decisions-solving-weighted-random-sorting-at-scale/",
      "title": "The Weight of Decisions: Solving Weighted Random Sorting at Scale",
      "content_html": "<p>Several years ago at Microsoft, I worked on a back-end service responsible for routing high volumes of traffic. The service functioned as a traffic dispatcher: we received an incoming request, determined the most appropriate downstream destination, and forwarded the payload.</p><p>Our routing decisions relied on a pool of destination services. To ensure reliability, we tracked ongoing statistics regarding the success rates of these partners. When a destination consistently processed requests successfully, we increased its traffic share. Conversely, if we observed frequent errors, we reduced its allocation.</p><h2 id=\"the-requirement-for-ordered-fail-over\" tabindex=\"-1\">The Requirement for Ordered Fail-over</h2><p>Routing in a distributed environment requires more than just selecting a single destination. Downstream partners occasionally fail to process specific items due to transient internal errors or malformed data. Because of this, we needed a &quot;fail-over&quot; list for every request. If the primary choice failed, the system needed an immediate second and third option ready to go.</p><p>This meant our routing logic had to produce a fully sorted list of all available destination services. We would attempt to send the request to the first service in the list; if that service returned an error, we moved to the second, and continued until the request succeeded or we exhausted the list.</p><h2 id=\"balancing-traffic-via-weighted-randomization\" tabindex=\"-1\">Balancing Traffic via Weighted Randomization</h2><p>A simple deterministic sort based on quality scores would create a &quot;starvation&quot; problem. If Service A had the highest success rate, a standard sort would consistently place it at the top. This would result in Service A receiving 100% of the initial traffic, leaving services B and C with no data points. Without ongoing &quot;exploration&quot; traffic to lower-ranked services, we could never observe if their performance had recovered or improved.</p><p>To maintain fresh statistics across the entire pool, we needed a weighted random sort, effectively a <strong>weighted permutation</strong>. Consider a scenario with three services and the following target distributions:</p><ul><li><strong>Service A:</strong> 50%</li><li><strong>Service B:</strong> 30%</li><li><strong>Service C:</strong> 20%</li></ul><p>The sorted list needed to respect these probabilities at every rank. For the first position, the chances are exactly as defined (50%, 30%, 20%). However, if Service A is selected for the first slot, the second slot must be filled by B or C based on their remaining relative weights. In this case, Service B should appear in the second position with a probability of 0.3 / (0.3 + 0.2) = 60%, while Service C takes the spot 40% of the time.</p><h2 id=\"implementation-logic\" tabindex=\"-1\">Implementation Logic</h2><p>Both the initial and the improved versions of our code use the following data structure to represent a destination:</p><pre class=\"language-csharp\"><code class=\"language-csharp\"><span class=\"token keyword\">public</span> <span class=\"token keyword\">class</span> <span class=\"token class-name\">Item</span>\n<span class=\"token punctuation\">{</span>\n\t<span class=\"token keyword\">public</span> <span class=\"token return-type class-name\"><span class=\"token keyword\">string</span></span> Name <span class=\"token punctuation\">{</span> <span class=\"token keyword\">get</span><span class=\"token punctuation\">;</span> <span class=\"token keyword\">set</span><span class=\"token punctuation\">;</span> <span class=\"token punctuation\">}</span>\n\t<span class=\"token keyword\">public</span> <span class=\"token return-type class-name\"><span class=\"token keyword\">double</span></span> Weight <span class=\"token punctuation\">{</span> <span class=\"token keyword\">get</span><span class=\"token punctuation\">;</span> <span class=\"token keyword\">set</span><span class=\"token punctuation\">;</span> <span class=\"token punctuation\">}</span>\n<span class=\"token punctuation\">}</span></code></pre><h2 id=\"the-intuition-and-the-initial-o(n2-log-n)-approach\" tabindex=\"-1\">The Intuition and the Initial O(N<sup>2</sup> log N) Approach</h2><p>I suspected a mathematical shortcut existed to calculate a single value for each item that, when sorted, would result in this exact distribution. This thought surfaced repeatedly over the years and never truly gave me peace. I felt certain a single-pass solution was possible, yet the specific formula remained out of reach. At the time, we settled on an iterative approach that picked items one by one.</p><p>In C#, the implementation relied on a &quot;weighted pick and remove&quot; loop. The computational cost was actually O(N<sup>2</sup> log N) in the version below because a full sort is performed inside a loop to pick just one item. While N = 10 renders this inefficiency invisible in production, the logic scales poorly for larger pools or high-throughput middleware.</p><pre class=\"language-csharp\"><code class=\"language-csharp\"><span class=\"token keyword\">public</span> <span class=\"token keyword\">static</span> <span class=\"token return-type class-name\">List<span class=\"token punctuation\">&lt;</span>T<span class=\"token punctuation\">></span></span> <span class=\"token generic-method\"><span class=\"token function\">InefficientWeightedRandomize</span><span class=\"token generic class-name\"><span class=\"token punctuation\">&lt;</span>T<span class=\"token punctuation\">></span></span></span><span class=\"token punctuation\">(</span><span class=\"token class-name\">IEnumerable<span class=\"token punctuation\">&lt;</span>T<span class=\"token punctuation\">></span></span> source<span class=\"token punctuation\">)</span>\n\t<span class=\"token keyword\">where</span> <span class=\"token class-name\">T</span> <span class=\"token punctuation\">:</span> <span class=\"token type-list\"><span class=\"token class-name\">Item</span></span>\n<span class=\"token punctuation\">{</span>\n\t<span class=\"token class-name\"><span class=\"token keyword\">var</span></span> remainingItems <span class=\"token operator\">=</span> source<span class=\"token punctuation\">.</span><span class=\"token function\">ToList</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\t<span class=\"token class-name\"><span class=\"token keyword\">var</span></span> result <span class=\"token operator\">=</span> <span class=\"token keyword\">new</span> <span class=\"token constructor-invocation class-name\">List<span class=\"token punctuation\">&lt;</span>T<span class=\"token punctuation\">></span></span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\n\t<span class=\"token keyword\">while</span> <span class=\"token punctuation\">(</span>remainingItems<span class=\"token punctuation\">.</span><span class=\"token function\">Any</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span>\n\t<span class=\"token punctuation\">{</span>\n\t\t<span class=\"token comment\">// Sort the source list by a random factor modified by weight</span>\n\t\t<span class=\"token class-name\"><span class=\"token keyword\">var</span></span> nextItem <span class=\"token operator\">=</span> remainingItems\n\t\t\t<span class=\"token punctuation\">.</span><span class=\"token function\">Select</span><span class=\"token punctuation\">(</span>item <span class=\"token operator\">=></span> <span class=\"token keyword\">new</span>\n\t\t\t<span class=\"token punctuation\">{</span>\n\t\t\t\tItem <span class=\"token operator\">=</span> item<span class=\"token punctuation\">,</span>\n\t\t\t\tSortKey <span class=\"token operator\">=</span> Random<span class=\"token punctuation\">.</span>Shared<span class=\"token punctuation\">.</span><span class=\"token function\">NextDouble</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">*</span> item<span class=\"token punctuation\">.</span>Weight\n\t\t\t<span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span>\n\t\t\t<span class=\"token punctuation\">.</span><span class=\"token function\">OrderByDescending</span><span class=\"token punctuation\">(</span>x <span class=\"token operator\">=></span> x<span class=\"token punctuation\">.</span>SortKey<span class=\"token punctuation\">)</span>\n\t\t\t<span class=\"token punctuation\">.</span><span class=\"token function\">First</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\n\t\tresult<span class=\"token punctuation\">.</span><span class=\"token function\">Add</span><span class=\"token punctuation\">(</span>nextItem<span class=\"token punctuation\">.</span>Item<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\t\tremainingItems<span class=\"token punctuation\">.</span><span class=\"token function\">Remove</span><span class=\"token punctuation\">(</span>nextItem<span class=\"token punctuation\">.</span>Item<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\t<span class=\"token punctuation\">}</span>\n\n\t<span class=\"token keyword\">return</span> result<span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span></code></pre><h2 id=\"the-efraimidis-spirakis-algorithm\" tabindex=\"-1\">The Efraimidis-Spirakis Algorithm</h2><p>Recently, I came across the <a href=\"https://utopia.duth.gr/~pefraimi/research/data/2007EncOfAlg.pdf\" target=\"_blank\" rel=\"noopener noreferrer\">Efraimidis-Spirakis Weighted Random Sampling paper</a>, which provides the mathematical proof for the &quot;one-pass sort&quot; I had originally envisioned. The paper demonstrates that calculating a key k<sub>i</sub> = u<sub>i</sub><sup>1/w<sub>i</sub></sup> (where <code>u</code> is a random value and <code>w</code> is the weight) and sorting by that key produces a mathematically perfect weighted random sample.</p><p>This technique is a specialized form of <strong><a href=\"https://en.wikipedia.org/wiki/Reservoir_sampling\" target=\"_blank\" rel=\"noopener noreferrer\">Reservoir Sampling</a></strong>. It allows us to process the list in O(N log N) time with a much cleaner implementation:</p><pre class=\"language-csharp\"><code class=\"language-csharp\"><span class=\"token keyword\">public</span> <span class=\"token keyword\">static</span> <span class=\"token return-type class-name\">List<span class=\"token punctuation\">&lt;</span>T<span class=\"token punctuation\">></span></span> <span class=\"token generic-method\"><span class=\"token function\">WeightedRandomize</span><span class=\"token generic class-name\"><span class=\"token punctuation\">&lt;</span>T<span class=\"token punctuation\">></span></span></span><span class=\"token punctuation\">(</span><span class=\"token class-name\">IEnumerable<span class=\"token punctuation\">&lt;</span>T<span class=\"token punctuation\">></span></span> source<span class=\"token punctuation\">)</span>\n\t<span class=\"token keyword\">where</span> <span class=\"token class-name\">T</span> <span class=\"token punctuation\">:</span> <span class=\"token type-list\"><span class=\"token class-name\">Item</span></span>\n<span class=\"token punctuation\">{</span>\n\t<span class=\"token keyword\">return</span> source\n\t\t<span class=\"token punctuation\">.</span><span class=\"token function\">Select</span><span class=\"token punctuation\">(</span>item <span class=\"token operator\">=></span> <span class=\"token keyword\">new</span>\n\t\t<span class=\"token punctuation\">{</span>\n\t\t\tItem <span class=\"token operator\">=</span> item<span class=\"token punctuation\">,</span>\n\t\t\tSortKey <span class=\"token operator\">=</span> Math<span class=\"token punctuation\">.</span><span class=\"token function\">Pow</span><span class=\"token punctuation\">(</span>\n\t\t\t\tRandom<span class=\"token punctuation\">.</span>Shared<span class=\"token punctuation\">.</span><span class=\"token function\">NextDouble</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">,</span>\n\t\t\t\t<span class=\"token number\">1.0</span> <span class=\"token operator\">/</span> item<span class=\"token punctuation\">.</span>Weight\n\t\t\t<span class=\"token punctuation\">)</span>\n\t\t<span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span>\n\t\t<span class=\"token punctuation\">.</span><span class=\"token function\">OrderByDescending</span><span class=\"token punctuation\">(</span>x <span class=\"token operator\">=></span> x<span class=\"token punctuation\">.</span>SortKey<span class=\"token punctuation\">)</span>\n\t\t<span class=\"token punctuation\">.</span><span class=\"token function\">Select</span><span class=\"token punctuation\">(</span>x <span class=\"token operator\">=></span> x<span class=\"token punctuation\">.</span>Item<span class=\"token punctuation\">)</span>\n\t\t<span class=\"token punctuation\">.</span><span class=\"token function\">ToList</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span></code></pre><h2 id=\"applications-for-streaming-data\" tabindex=\"-1\">Applications for Streaming Data</h2><p>The Efraimidis-Spirakis algorithm offers an interesting property: it works on data streams without requiring the full list to be present at the start. Since each item's <code>SortKey</code> is calculated independently, we can assign keys as items arrive. Once the stream ends, a single pass through the data (or maintaining a min-heap for a &quot;Top K&quot; selection) yields the result.</p><p>For the team back at Microsoft still managing these services, this is a small but elegant improvement. While N = 10 doesn't demand high-performance algorithms, replacing the O(N<sup>2</sup>) loop with a proper weighted sort aligns the implementation with the intended mathematical model. I suspect there might even be an old research ticket regarding this specific efficiency improvement sitting in the backlog, likely created by me before I moved on. It might be time to finally close it.</p>",
      "date_published": "2026-01-07T00:00:00Z"
    }
    ,
    {
      "id": "https://byteaether.github.io/2025/building-an-enterprise-data-access-layer-automated-user-auditing-and-series-wrap-up/",
      "url": "https://byteaether.github.io/2025/building-an-enterprise-data-access-layer-automated-user-auditing-and-series-wrap-up/",
      "title": "Building an Enterprise Data Access Layer: Automated User Auditing and Series Wrap-up",
      "content_html": "<p>Welcome to the seventh and final article in our series on building an enterprise-grade Data Access Layer (DAL) in C#. Over the last six posts, we have methodically built a robust, automated, and secure DAL using a database-first philosophy with C# and LinqToDB.</p><p>We've tackled soft-deletes, timestamp auditing, multi-tenancy, and even complex projected row-level security. In our <a href=\"https://byteaether.github.io/2025/building-an-enterprise-data-access-layer-the-foundation/\">very first article</a>, we set a high bar for the capabilities our DAL must provide. Today, we implement the final missing piece: <strong>automated user auditing</strong> (<code>CreatedByUser</code> / <code>ModifiedByUser</code>).</p><p>Once that's in place, we'll hold a full retrospective to see how our final architecture stacks up against our original goals, and finally, we'll look at what challenges lie beyond this series.</p><h2 id=\"implementing-user-auditing\" tabindex=\"-1\">Implementing User Auditing</h2><p>So far, our audit fields (<code>CreatedAt</code>, <code>ModifiedAt</code>) tell us <em>when</em> a record was changed. User auditing completes the picture by telling us <em>who</em> made that change. This is a common requirement for compliance, traceability, and debugging.</p><p>Our goal is to automatically populate <code>CreatedByUserId</code> on insert and <code>ModifiedByUserId</code> on insert and update, using the <code>UserId</code> from our request-scoped context. We will follow the exact same pattern of &quot;behavioral interfaces&quot; that we established in <a href=\"https://byteaether.github.io/2025/building-an-enterprise-data-access-layer-automated-auditing/\">Part 3 (Auditing)</a> and <a href=\"https://byteaether.github.io/2025/building-an-enterprise-data-access-layer-automated-soft-delete/\">Part 4 (Soft-Delete)</a>.</p><h3 id=\"the-schema-and-interface-contracts\" tabindex=\"-1\">The Schema and Interface Contracts</h3><p>Following our database-first approach, we first update the database schema. We add <code>created_by_user_id ulid not null</code> and <code>modified_by_user_id ulid not null</code> columns to the <code>post</code> and <code>comment</code> tables, both with foreign keys referencing the <code>user</code> table.</p><p>Next, we define the C# contracts in <code>DAL.Base/EntityBehavior/</code>:</p><pre class=\"language-csharp\"><code class=\"language-csharp\"><span class=\"token keyword\">namespace</span> <span class=\"token namespace\">DAL<span class=\"token punctuation\">.</span>Base<span class=\"token punctuation\">.</span>EntityBehavior</span><span class=\"token punctuation\">;</span>\n\n<span class=\"token keyword\">public</span> <span class=\"token keyword\">interface</span> <span class=\"token class-name\">IUserCreatable</span>\n<span class=\"token punctuation\">{</span>\n\t<span class=\"token return-type class-name\">Ulid</span> CreatedByUserId <span class=\"token punctuation\">{</span> <span class=\"token keyword\">get</span><span class=\"token punctuation\">;</span> <span class=\"token keyword\">set</span><span class=\"token punctuation\">;</span> <span class=\"token punctuation\">}</span>\n<span class=\"token punctuation\">}</span></code></pre><pre class=\"language-csharp\"><code class=\"language-csharp\"><span class=\"token keyword\">namespace</span> <span class=\"token namespace\">DAL<span class=\"token punctuation\">.</span>Base<span class=\"token punctuation\">.</span>EntityBehavior</span><span class=\"token punctuation\">;</span>\n\n<span class=\"token keyword\">public</span> <span class=\"token keyword\">interface</span> <span class=\"token class-name\">IUserModifiable</span>\n<span class=\"token punctuation\">{</span>\n\t<span class=\"token return-type class-name\">Ulid</span> ModifiedByUserId <span class=\"token punctuation\">{</span> <span class=\"token keyword\">get</span><span class=\"token punctuation\">;</span> <span class=\"token keyword\">set</span><span class=\"token punctuation\">;</span> <span class=\"token punctuation\">}</span>\n<span class=\"token punctuation\">}</span></code></pre><p>These simple interfaces define the properties our DAL will automatically manage.</p><h3 id=\"automated-scaffolding\" tabindex=\"-1\">Automated Scaffolding</h3><p>With the database and interfaces in place, we update our custom scaffolding interceptor (<code>DAL.ScaffoldInterceptor/Interceptor.cs</code>).</p><p>Just as it was configured to detect <code>created_at</code> and attach <code>ICreatable</code>, we've now taught it to look for <code>created_by_user_id</code> and <code>modified_by_user_id</code> columns. When it finds them, it automatically adds <code>IUserCreatable</code> and <code>IUserModifiable</code> to the generated partial entity classes in <code>DbCtx.generated.cs</code>.</p><p>This is the power of our architecture: the database schema remains the single source of truth. A schema change, followed by a re-scaffold, is all that's needed to make the C# code aware of this new behavior.</p><h3 id=\"updating-the-crud-extensions\" tabindex=\"-1\">Updating the CRUD Extensions</h3><p>The final step is to update our <code>CrudExtensions.cs</code> file to enforce the logic. We need to fetch the current user's ID from our <code>IDbCtx</code> (which we added in <a href=\"https://byteaether.github.io/2025/building-an-enterprise-data-access-layer-composable-row-level-security/\">Part 6</a>) and assign it.</p><p>We update <code>CreateAsync</code>, <code>ModifyAsync</code> (for single entity), and <code>ModifyAsync</code> (for fluent queries) to handle these new interfaces.</p><pre class=\"language-csharp\"><code class=\"language-csharp\"><span class=\"token keyword\">public</span> <span class=\"token keyword\">static</span> <span class=\"token keyword\">async</span> <span class=\"token return-type class-name\">Task<span class=\"token punctuation\">&lt;</span><span class=\"token keyword\">long</span><span class=\"token punctuation\">></span></span> <span class=\"token generic-method\"><span class=\"token function\">CreateAsync</span><span class=\"token generic class-name\"><span class=\"token punctuation\">&lt;</span>T<span class=\"token punctuation\">></span></span></span><span class=\"token punctuation\">(</span>\n\t<span class=\"token keyword\">this</span> <span class=\"token class-name\">IDbCtx</span> ctx<span class=\"token punctuation\">,</span>\n\t<span class=\"token class-name\">IEnumerable<span class=\"token punctuation\">&lt;</span>T<span class=\"token punctuation\">></span></span> entities<span class=\"token punctuation\">,</span>\n\t<span class=\"token class-name\">CancellationToken</span> cancellationToken <span class=\"token operator\">=</span> <span class=\"token keyword\">default</span>\n<span class=\"token punctuation\">)</span>\n\t<span class=\"token keyword\">where</span> <span class=\"token class-name\">T</span> <span class=\"token punctuation\">:</span> <span class=\"token type-list\"><span class=\"token keyword\">class</span><span class=\"token punctuation\">,</span> <span class=\"token class-name\">IEntity</span></span>\n\t<span class=\"token operator\">=></span> <span class=\"token punctuation\">(</span>\n\t\t<span class=\"token keyword\">await</span> ctx<span class=\"token punctuation\">.</span><span class=\"token function\">BulkCopyAsync</span><span class=\"token punctuation\">(</span>\n\t\t\tentities<span class=\"token punctuation\">.</span><span class=\"token function\">Select</span><span class=\"token punctuation\">(</span>e <span class=\"token operator\">=></span>\n\t\t\t\t<span class=\"token punctuation\">{</span>\n\t\t\t\t\t<span class=\"token comment\">// Timestamp Auditing (from Part 3)</span>\n\t\t\t\t\t<span class=\"token keyword\">if</span> <span class=\"token punctuation\">(</span>\n\t\t\t\t\t\te <span class=\"token keyword\">is</span> <span class=\"token class-name\">ICreatable</span> creatable\n\t\t\t\t\t\t<span class=\"token operator\">&amp;&amp;</span> creatable<span class=\"token punctuation\">.</span>CreatedAt <span class=\"token operator\">==</span> <span class=\"token keyword\">default</span>\n\t\t\t\t\t<span class=\"token punctuation\">)</span>\n\t\t\t\t\t<span class=\"token punctuation\">{</span>\n\t\t\t\t\t\tcreatable<span class=\"token punctuation\">.</span>CreatedAt <span class=\"token operator\">=</span> DateTime<span class=\"token punctuation\">.</span>UtcNow<span class=\"token punctuation\">;</span>\n\t\t\t\t\t<span class=\"token punctuation\">}</span>\n\n\t\t\t\t\t<span class=\"token comment\">// NEW: User Create Auditing</span>\n\t\t\t\t\t<span class=\"token keyword\">if</span> <span class=\"token punctuation\">(</span>\n\t\t\t\t\t\te <span class=\"token keyword\">is</span> <span class=\"token class-name\">IUserCreatable</span> userCreatable\n\t\t\t\t\t\t<span class=\"token operator\">&amp;&amp;</span> userCreatable<span class=\"token punctuation\">.</span>CreatedByUserId <span class=\"token operator\">==</span> <span class=\"token keyword\">default</span>\n\t\t\t\t\t<span class=\"token punctuation\">)</span>\n\t\t\t\t\t<span class=\"token punctuation\">{</span>\n\t\t\t\t\t\tuserCreatable<span class=\"token punctuation\">.</span>CreatedByUserId <span class=\"token operator\">=</span> ctx<span class=\"token punctuation\">.</span>Attributes<span class=\"token punctuation\">.</span>UserId <span class=\"token operator\">??</span> Ulid<span class=\"token punctuation\">.</span>Empty<span class=\"token punctuation\">;</span>\n\t\t\t\t\t<span class=\"token punctuation\">}</span>\n\n\t\t\t\t\t<span class=\"token comment\">// Timestamp Auditing (from Part 3)</span>\n\t\t\t\t\t<span class=\"token keyword\">if</span> <span class=\"token punctuation\">(</span>\n\t\t\t\t\t\te <span class=\"token keyword\">is</span> <span class=\"token class-name\">IModifiable</span> updateable\n\t\t\t\t\t\t<span class=\"token operator\">&amp;&amp;</span> updateable<span class=\"token punctuation\">.</span>ModifiedAt <span class=\"token operator\">==</span> <span class=\"token keyword\">default</span>\n\t\t\t\t\t<span class=\"token punctuation\">)</span>\n\t\t\t\t\t<span class=\"token punctuation\">{</span>\n\t\t\t\t\t\tupdateable<span class=\"token punctuation\">.</span>ModifiedAt <span class=\"token operator\">=</span> DateTime<span class=\"token punctuation\">.</span>UtcNow<span class=\"token punctuation\">;</span>\n\t\t\t\t\t<span class=\"token punctuation\">}</span>\n\n\t\t\t\t\t<span class=\"token comment\">// NEW: User Modify Auditing</span>\n\t\t\t\t\t<span class=\"token keyword\">if</span> <span class=\"token punctuation\">(</span>\n\t\t\t\t\t\te <span class=\"token keyword\">is</span> <span class=\"token class-name\">IUserModifiable</span> userUpdateable\n\t\t\t\t\t\t<span class=\"token operator\">&amp;&amp;</span> userUpdateable<span class=\"token punctuation\">.</span>ModifiedByUserId <span class=\"token operator\">==</span> <span class=\"token keyword\">default</span>\n\t\t\t\t\t<span class=\"token punctuation\">)</span>\n\t\t\t\t\t<span class=\"token punctuation\">{</span>\n\t\t\t\t\t\tuserUpdateable<span class=\"token punctuation\">.</span>ModifiedByUserId <span class=\"token operator\">=</span> ctx<span class=\"token punctuation\">.</span>Attributes<span class=\"token punctuation\">.</span>UserId <span class=\"token operator\">??</span> Ulid<span class=\"token punctuation\">.</span>Empty<span class=\"token punctuation\">;</span>\n\t\t\t\t\t<span class=\"token punctuation\">}</span>\n\n\t\t\t\t\t<span class=\"token keyword\">return</span> e<span class=\"token punctuation\">;</span>\n\t\t\t\t<span class=\"token punctuation\">}</span>\n\t\t\t<span class=\"token punctuation\">)</span><span class=\"token punctuation\">,</span>\n\t\t\tcancellationToken\n\t\t<span class=\"token punctuation\">)</span>\n\t<span class=\"token punctuation\">)</span><span class=\"token punctuation\">.</span>RowsCopied<span class=\"token punctuation\">;</span>\n\n<span class=\"token keyword\">public</span> <span class=\"token keyword\">static</span> <span class=\"token return-type class-name\">Task<span class=\"token punctuation\">&lt;</span><span class=\"token keyword\">int</span><span class=\"token punctuation\">></span></span> <span class=\"token generic-method\"><span class=\"token function\">ModifyAsync</span><span class=\"token generic class-name\"><span class=\"token punctuation\">&lt;</span>T<span class=\"token punctuation\">></span></span></span><span class=\"token punctuation\">(</span>\n\t<span class=\"token keyword\">this</span> <span class=\"token class-name\">IDbCtx</span> ctx<span class=\"token punctuation\">,</span>\n\t<span class=\"token class-name\">T</span> entity<span class=\"token punctuation\">,</span>\n\t<span class=\"token class-name\">CancellationToken</span> cancellationToken <span class=\"token operator\">=</span> <span class=\"token keyword\">default</span>\n<span class=\"token punctuation\">)</span>\n\t<span class=\"token keyword\">where</span> <span class=\"token class-name\">T</span> <span class=\"token punctuation\">:</span> <span class=\"token type-list\"><span class=\"token keyword\">class</span><span class=\"token punctuation\">,</span> <span class=\"token class-name\">IEntity</span></span>\n<span class=\"token punctuation\">{</span>\n\t<span class=\"token comment\">// Timestamp Auditing (from Part 3)</span>\n\t<span class=\"token keyword\">if</span> <span class=\"token punctuation\">(</span>entity <span class=\"token keyword\">is</span> <span class=\"token class-name\">IModifiable</span> updateable<span class=\"token punctuation\">)</span>\n\t<span class=\"token punctuation\">{</span>\n\t\tupdateable<span class=\"token punctuation\">.</span>ModifiedAt <span class=\"token operator\">=</span> DateTime<span class=\"token punctuation\">.</span>UtcNow<span class=\"token punctuation\">;</span>\n\t<span class=\"token punctuation\">}</span>\n\n\t<span class=\"token comment\">// NEW: User Modify Auditing</span>\n\t<span class=\"token keyword\">if</span> <span class=\"token punctuation\">(</span>entity <span class=\"token keyword\">is</span> <span class=\"token class-name\">IUserModifiable</span> userUpdateable<span class=\"token punctuation\">)</span>\n\t<span class=\"token punctuation\">{</span>\n\t\tuserUpdateable<span class=\"token punctuation\">.</span>ModifiedByUserId <span class=\"token operator\">=</span> ctx<span class=\"token punctuation\">.</span>Attributes<span class=\"token punctuation\">.</span>UserId <span class=\"token operator\">??</span> Ulid<span class=\"token punctuation\">.</span>Empty<span class=\"token punctuation\">;</span>\n\t<span class=\"token punctuation\">}</span>\n\n\t<span class=\"token keyword\">return</span> ctx<span class=\"token punctuation\">.</span><span class=\"token generic-method\"><span class=\"token function\">GetTable</span><span class=\"token generic class-name\"><span class=\"token punctuation\">&lt;</span>T<span class=\"token punctuation\">></span></span></span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span>\n\t\t<span class=\"token punctuation\">.</span><span class=\"token function\">UpdateOptimisticAsync</span><span class=\"token punctuation\">(</span>entity<span class=\"token punctuation\">,</span> cancellationToken<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span></code></pre><h3 id=\"the-iupdatable-nuance\" tabindex=\"-1\">The <code>IUpdatable</code> Nuance</h3><p>The fluent <code>ModifyAsync</code> extension (which operates on <code>IUpdatable&lt;T&gt;</code>) presents a unique challenge. This method is what allows us to write code like <code>ctx.GetTable&lt;Post&gt;().Where(p =&gt; p.Id == id).ModifyAsync()</code>.</p><p>To inject the <code>ModifiedByUserId</code> value, we need the <code>IDbCtx</code> instance to get the <code>ctx.Attributes.UserId</code>. However, the <code>IUpdatable&lt;T&gt;</code> (which is essentially an <code>IQueryable</code>) doesn't expose its parent context directly.</p><p>To solve this, we use a helper provided by LinqToDB:</p><pre class=\"language-csharp\"><code class=\"language-csharp\"><span class=\"token keyword\">public</span> <span class=\"token keyword\">static</span> <span class=\"token return-type class-name\">Task<span class=\"token punctuation\">&lt;</span><span class=\"token keyword\">int</span><span class=\"token punctuation\">></span></span> <span class=\"token generic-method\"><span class=\"token function\">ModifyAsync</span><span class=\"token generic class-name\"><span class=\"token punctuation\">&lt;</span>T<span class=\"token punctuation\">></span></span></span><span class=\"token punctuation\">(</span>\n\t<span class=\"token keyword\">this</span> <span class=\"token class-name\">IUpdatable<span class=\"token punctuation\">&lt;</span>T<span class=\"token punctuation\">></span></span> source<span class=\"token punctuation\">,</span>\n\t<span class=\"token class-name\">CancellationToken</span> cancellationToken <span class=\"token operator\">=</span> <span class=\"token keyword\">default</span>\n<span class=\"token punctuation\">)</span>\n\t<span class=\"token keyword\">where</span> <span class=\"token class-name\">T</span> <span class=\"token punctuation\">:</span> <span class=\"token type-list\"><span class=\"token keyword\">class</span></span>\n<span class=\"token punctuation\">{</span>\n\t<span class=\"token comment\">// Timestamp Auditing (from Part 3)</span>\n\t<span class=\"token keyword\">if</span> <span class=\"token punctuation\">(</span><span class=\"token keyword\">typeof</span><span class=\"token punctuation\">(</span><span class=\"token type-expression class-name\">IModifiable</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">.</span><span class=\"token function\">IsAssignableFrom</span><span class=\"token punctuation\">(</span><span class=\"token keyword\">typeof</span><span class=\"token punctuation\">(</span><span class=\"token type-expression class-name\">T</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span>\n\t<span class=\"token punctuation\">{</span>\n\t\tsource <span class=\"token operator\">=</span> source<span class=\"token punctuation\">.</span><span class=\"token function\">Set</span><span class=\"token punctuation\">(</span>\n\t\t\tx <span class=\"token operator\">=></span> Sql<span class=\"token punctuation\">.</span><span class=\"token generic-method\"><span class=\"token function\">Property</span><span class=\"token generic class-name\"><span class=\"token punctuation\">&lt;</span>DateTime<span class=\"token punctuation\">></span></span></span><span class=\"token punctuation\">(</span>x<span class=\"token punctuation\">,</span> <span class=\"token keyword\">nameof</span><span class=\"token punctuation\">(</span>IModifiable<span class=\"token punctuation\">.</span>ModifiedAt<span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">,</span>\n\t\t\tDateTime<span class=\"token punctuation\">.</span>UtcNow\n\t\t<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\t<span class=\"token punctuation\">}</span>\n\n\t<span class=\"token comment\">// NEW: User Modify Auditing</span>\n\t<span class=\"token keyword\">if</span> <span class=\"token punctuation\">(</span><span class=\"token keyword\">typeof</span><span class=\"token punctuation\">(</span><span class=\"token type-expression class-name\">IUserModifiable</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">.</span><span class=\"token function\">IsAssignableFrom</span><span class=\"token punctuation\">(</span><span class=\"token keyword\">typeof</span><span class=\"token punctuation\">(</span><span class=\"token type-expression class-name\">T</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span>\n\t<span class=\"token punctuation\">{</span>\n\t\t<span class=\"token comment\">// Get the DbCtx from the query</span>\n\t\t<span class=\"token class-name\"><span class=\"token keyword\">var</span></span> dbCtx <span class=\"token operator\">=</span> Internals<span class=\"token punctuation\">.</span><span class=\"token function\">GetDataContext</span><span class=\"token punctuation\">(</span>source<span class=\"token punctuation\">)</span> <span class=\"token keyword\">as</span> <span class=\"token class-name\">IDbCtx</span><span class=\"token punctuation\">;</span>\n\t\tsource <span class=\"token operator\">=</span> source<span class=\"token punctuation\">.</span><span class=\"token function\">Set</span><span class=\"token punctuation\">(</span>\n\t\t\tx <span class=\"token operator\">=></span> Sql<span class=\"token punctuation\">.</span><span class=\"token generic-method\"><span class=\"token function\">Property</span><span class=\"token generic class-name\"><span class=\"token punctuation\">&lt;</span>Ulid<span class=\"token punctuation\">></span></span></span><span class=\"token punctuation\">(</span>x<span class=\"token punctuation\">,</span> <span class=\"token keyword\">nameof</span><span class=\"token punctuation\">(</span>IUserModifiable<span class=\"token punctuation\">.</span>ModifiedByUserId<span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">,</span>\n\t\t\tdbCtx<span class=\"token punctuation\">?.</span>Attributes<span class=\"token punctuation\">.</span>UserId <span class=\"token operator\">??</span> Ulid<span class=\"token punctuation\">.</span>Empty\n\t\t<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\t<span class=\"token punctuation\">}</span>\n\n\t<span class=\"token keyword\">return</span> source<span class=\"token punctuation\">.</span><span class=\"token function\">UpdateAsync</span><span class=\"token punctuation\">(</span>cancellationToken<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span></code></pre><p>The key part is <code>(LinqToDB.Internal.Linq.)Internals.GetDataContext(source)</code>. While using an <code>Internal</code> namespace is generally something to be wary of, it is a pragmatic and necessary solution in this advanced scenario. It allows us to bridge our fluent query API with our request-scoped context, ensuring user auditing is applied even during batch <code>UPDATE</code> operations.</p><p>And with that, our final automated behavior is implemented.</p><h3 id=\"the-result%3A-a-fully-automated-query\" tabindex=\"-1\">The Result: A Fully Automated Query</h3><p>To see all our work come together, let's look at the simple <code>ModifyAsync</code> call from our <code>Program.cs</code> that updates a <code>Comment</code>:</p><pre class=\"language-csharp\"><code class=\"language-csharp\"><span class=\"token comment\">// Try to update a protected comment entity</span>\n<span class=\"token keyword\">await</span> ctx<span class=\"token punctuation\">.</span><span class=\"token generic-method\"><span class=\"token function\">GetTable</span><span class=\"token generic class-name\"><span class=\"token punctuation\">&lt;</span>Comment<span class=\"token punctuation\">></span></span></span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span>\n\t<span class=\"token punctuation\">.</span><span class=\"token function\">Set</span><span class=\"token punctuation\">(</span>x <span class=\"token operator\">=></span> x<span class=\"token punctuation\">.</span>Content<span class=\"token punctuation\">,</span> <span class=\"token string\">\"asd123\"</span><span class=\"token punctuation\">)</span>\n\t<span class=\"token punctuation\">.</span><span class=\"token function\">ModifyAsync</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span></code></pre><p>When this C# code executes, our DAL intercepts it and, using all the logic from this series, generates the following comprehensive SQL query:</p><pre class=\"language-sql\"><code class=\"language-sql\"><span class=\"token keyword\">UPDATE</span>\n\t<span class=\"token punctuation\">[</span><span class=\"token keyword\">comment</span><span class=\"token punctuation\">]</span>\n<span class=\"token keyword\">SET</span>\n\t<span class=\"token punctuation\">[</span>content<span class=\"token punctuation\">]</span> <span class=\"token operator\">=</span> <span class=\"token string\">'asd123'</span><span class=\"token punctuation\">,</span>\n\t<span class=\"token punctuation\">[</span>modified_at<span class=\"token punctuation\">]</span> <span class=\"token operator\">=</span> <span class=\"token string\">'2025-11-12 15:31:11.972'</span><span class=\"token punctuation\">,</span>\n\t<span class=\"token punctuation\">[</span>modified_by_user_id<span class=\"token punctuation\">]</span> <span class=\"token operator\">=</span> X<span class=\"token string\">'019A78B1378CC2EC0F1736EE8D4F1E8F'</span>\n<span class=\"token keyword\">FROM</span>\n\t<span class=\"token punctuation\">[</span>post<span class=\"token punctuation\">]</span> <span class=\"token punctuation\">[</span>a_Post<span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span>\n\t<span class=\"token punctuation\">[</span><span class=\"token keyword\">user</span><span class=\"token punctuation\">]</span> <span class=\"token punctuation\">[</span>a_User<span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span>\n\t<span class=\"token punctuation\">[</span>permission<span class=\"token punctuation\">]</span> <span class=\"token punctuation\">[</span>b<span class=\"token punctuation\">]</span>\n<span class=\"token keyword\">WHERE</span>\n\t<span class=\"token punctuation\">[</span>a_User<span class=\"token punctuation\">]</span><span class=\"token punctuation\">.</span><span class=\"token punctuation\">[</span>removed_at<span class=\"token punctuation\">]</span> <span class=\"token operator\">IS</span> <span class=\"token boolean\">NULL</span> <span class=\"token operator\">AND</span>\n\t<span class=\"token punctuation\">[</span>a_User<span class=\"token punctuation\">]</span><span class=\"token punctuation\">.</span><span class=\"token punctuation\">[</span>tenant_id<span class=\"token punctuation\">]</span> <span class=\"token operator\">=</span> X<span class=\"token string\">'019A78B13787E6060BF9BA9A51DAA2C6'</span> <span class=\"token operator\">AND</span>\n\t<span class=\"token punctuation\">[</span>a_Post<span class=\"token punctuation\">]</span><span class=\"token punctuation\">.</span><span class=\"token punctuation\">[</span>removed_at<span class=\"token punctuation\">]</span> <span class=\"token operator\">IS</span> <span class=\"token boolean\">NULL</span> <span class=\"token operator\">AND</span>\n\t<span class=\"token punctuation\">[</span><span class=\"token keyword\">comment</span><span class=\"token punctuation\">]</span><span class=\"token punctuation\">.</span><span class=\"token punctuation\">[</span>removed_at<span class=\"token punctuation\">]</span> <span class=\"token operator\">IS</span> <span class=\"token boolean\">NULL</span> <span class=\"token operator\">AND</span>\n\t<span class=\"token punctuation\">[</span><span class=\"token keyword\">comment</span><span class=\"token punctuation\">]</span><span class=\"token punctuation\">.</span><span class=\"token punctuation\">[</span>post_id<span class=\"token punctuation\">]</span> <span class=\"token operator\">=</span> <span class=\"token punctuation\">[</span>a_Post<span class=\"token punctuation\">]</span><span class=\"token punctuation\">.</span><span class=\"token punctuation\">[</span>id<span class=\"token punctuation\">]</span> <span class=\"token operator\">AND</span>\n\t<span class=\"token punctuation\">[</span>a_Post<span class=\"token punctuation\">]</span><span class=\"token punctuation\">.</span><span class=\"token punctuation\">[</span>user_id<span class=\"token punctuation\">]</span> <span class=\"token operator\">=</span> <span class=\"token punctuation\">[</span>a_User<span class=\"token punctuation\">]</span><span class=\"token punctuation\">.</span><span class=\"token punctuation\">[</span>id<span class=\"token punctuation\">]</span> <span class=\"token operator\">AND</span>\n\t<span class=\"token punctuation\">[</span>b<span class=\"token punctuation\">]</span><span class=\"token punctuation\">.</span><span class=\"token punctuation\">[</span>object_id<span class=\"token punctuation\">]</span> <span class=\"token operator\">=</span> <span class=\"token punctuation\">[</span>a_Post<span class=\"token punctuation\">]</span><span class=\"token punctuation\">.</span><span class=\"token punctuation\">[</span>id<span class=\"token punctuation\">]</span> <span class=\"token operator\">AND</span>\n\t<span class=\"token punctuation\">[</span>b<span class=\"token punctuation\">]</span><span class=\"token punctuation\">.</span><span class=\"token punctuation\">[</span>subject_id<span class=\"token punctuation\">]</span> <span class=\"token operator\">=</span> X<span class=\"token string\">'019A78B1378CC2EC0F1736EE8D4F1E8F'</span></code></pre><p>This single <code>UPDATE</code> statement perfectly demonstrates our entire architecture working in harmony. The <code>SET</code> clause not only updates the <code>content</code> but also automatically injects <strong><code>modified_at</code></strong> (from <a href=\"https://byteaether.github.io/2025/building-an-enterprise-data-access-layer-automated-auditing/\">Part 3</a>) and our new <strong><code>modified_by_user_id</code></strong> field.</p><p>Furthermore, the <code>WHERE</code> clause is a composition of all our global filters:</p><ul><li><strong>Soft-Delete (<code>removed_at IS NULL</code>)</strong> on <code>user</code>, <code>post</code>, and <code>comment</code>.</li><li><strong>Multi-Tenancy (<code>tenant_id = ...</code>)</strong> on the <code>user</code> table (a projected filter).</li><li><strong>Row-Level Security (<code>[b].[object_id] = ...</code>)</strong> by joining the <code>permission</code> table (another projected filter).</li></ul><p>This is the automation we set out to build, all abstracted from the developer who just called <code>.ModifyAsync()</code>.</p><h2 id=\"achieving-our-goals\" tabindex=\"-1\">Achieving Our Goals</h2><p>In our <a href=\"https://byteaether.github.io/2025/building-an-enterprise-data-access-layer-the-foundation/\">first article</a>, we defined a clear set of requirements for an enterprise DAL. Our mission was to automate cross-cutting concerns to prevent errors and reduce technical debt. Let's review our original checklist.</p><ul><li><p><strong>Soft-Delete:</strong></p><ul><li><strong>Goal:</strong> Automatically filter out records marked with a <code>DeletedAt</code> timestamp on all read operations.</li><li><strong>Achieved:</strong> Yes. In <a href=\"https://byteaether.github.io/2025/building-an-enterprise-data-access-layer-automated-soft-delete/\">Part 4</a>, we implemented <code>IRemovable</code> and a composable global query filter system. This system automatically and ubiquitously applies the <code>WHERE [removed_at] IS NULL</code> clause to all queries, including joins. We also overrode <code>DeleteAsync</code> to transparently convert deletes into updates.</li></ul></li><li><p><strong>Timestamp Auditing:</strong></p><ul><li><strong>Goal:</strong> Automatically populate <code>CreatedAt</code> and <code>ModifiedAt</code> timestamps.</li><li><strong>Achieved:</strong> Yes. In <a href=\"https://byteaether.github.io/2025/building-an-enterprise-data-access-layer-automated-auditing/\">Part 3</a>, we used <code>ICreatable</code> and <code>IModifiable</code> interfaces and <code>CrudExtensions</code> to inject the current timestamp during create and modify operations.</li></ul></li><li><p><strong>User Auditing:</strong></p><ul><li><strong>Goal:</strong> Automatically populate <code>CreatedByUserId</code> and <code>ModifiedByUserId</code> with the current user's ID.</li><li><strong>Achieved:</strong> Yes. We just completed this in the first section of this article, using the exact same robust pattern (<code>IUserCreatable</code>, <code>IUserModifiable</code>).</li></ul></li><li><p><strong>Multi-Tenancy Filtering:</strong></p><ul><li><strong>Goal:</strong> Automatically inject a <code>WHERE TenantId = [CurrentTenantId]</code> clause for all tenant-owned entities.</li><li><strong>Achieved:</strong> Yes. In <a href=\"https://byteaether.github.io/2025/building-an-enterprise-data-access-layer-composable-multi-tenancy-filtering/\">Part 5</a>, we leveraged our composable filter system by introducing the <code>ITenanted</code> interface. This filter reads the <code>TenantId</code> from our request-scoped <code>DbCtxAttributes</code> and applies the filter.</li></ul></li><li><p><strong>Projected Data Support:</strong></p><ul><li><strong>Goal:</strong> Resolve contextual IDs (like <code>TenantId</code> or permission IDs) even when they are on a related entity.</li><li><strong>Achieved:</strong> Yes. This was a critical success and a key differentiator for LinqToDB.<ol><li><strong>Projected Tenancy:</strong> In <a href=\"https://byteaether.github.io/2025/building-an-enterprise-data-access-layer-composable-multi-tenancy-filtering/\">Part 5</a>, our <code>Post</code> entity implemented <code>ITenanted</code> by defining its <code>TenantId</code> via an <code>[ExpressionMethod]</code> that navigated to its parent <code>User</code> (<code>x =&gt; x.User.TenantId</code>).</li><li><strong>Projected Permissions:</strong> In <a href=\"https://byteaether.github.io/2025/building-an-enterprise-data-access-layer-composable-row-level-security/\">Part 6</a>, our <code>Comment</code> entity implemented <code>IProtected</code> by projecting its permission <code>ObjectId</code> from its parent <code>Post</code> (<code>x =&gt; x.Post.Id</code>).</li></ol></li><li>In both cases, LinqToDB correctly translated these C# expressions into the necessary SQL <code>INNER JOIN</code>s and <code>WHERE</code> clauses.</li></ul></li><li><p><strong>Entity-Based Permissions (RLS):</strong></p><ul><li><strong>Goal:</strong> Automatically filter rows that the current user is not authorized to see.</li><li><strong>Achieved:</strong> Yes. In <a href=\"https://byteaether.github.io/2025/building-an-enterprise-data-access-layer-composable-row-level-security/\">Part 6</a>, we built our final and most complex filter, <code>IProtected</code>. This filter joins against the <code>permission</code> table using the current <code>UserId</code>, effectively implementing row-level security transparently.</li></ul></li></ul><p><strong>Verdict:</strong> We successfully implemented 100% of our initial goals. The final composed query, which LinqToDB generates by combining all these filters, is a testament to the power of this composable, interface-driven architecture.</p><h2 id=\"future-ideas\" tabindex=\"-1\">Future Ideas</h2><p>While this series is complete, the work of a systems architect is never truly finished. Our DAL is robust, but it exists within a larger ecosystem. Here are a few topics worth exploring next:</p><ul><li><strong>Database Migrations and CI Pipelines:</strong> We've perfected the <em>runtime</em> behavior of our DAL, but we've manually managed schema changes. A critical next step would be to build a full-fledged CI/CD pipeline. This would involve using a migration tool to manage schema evolution, and building a pipeline that runs tests, applies migrations, performs health checks, and supports automatic rollbacks.</li><li><strong>Full History Support:</strong> In our first article, we deliberately scoped out full history support, settling for soft-deletes and simple audit stamps. For systems with deep compliance or analytical needs (e.g., &quot;what did this record look like on June 1st?&quot;), this isn't enough. A fascinating challenge would be to implement a full temporal history or versioning system directly within the DAL.</li><li><strong>Replicating this with EF Core:</strong> Replicating this architecture in EF Core, .NET's most popular ORM, would be a significant challenge. While its Global Query Filters are identical, necessitating the same composable filter system, other aspects diverge. EF Core's &quot;code-first&quot; preference and reliance on a Change Tracker (Unit of Work) would fundamentally alter our scaffolding and <code>CrudExtensions</code> implementation. The largest obstacle would be projected properties, as EF Core lacks native support for <code>[ExpressionMethod]</code>, requiring a deep investigation into community libraries to handle our projected tenancy and RLS.</li></ul><h2 id=\"conclusion\" tabindex=\"-1\">Conclusion</h2><p>Thank you for following this series. We began with an ambitious set of goals for a &quot;perfect&quot; DAL, and through seven articles, we've built it piece by piece. We have a Data Access Layer that is truly &quot;enterprise-ready&quot; - it is secure by default, resilient to common errors, and abstracts immense complexity away from the business logic.</p><p>The final code for this article, and the entire series, is available on GitHub.</p><p><strong>Full Source Code: <a href=\"https://github.com/ByteAether/EnterpriseDal/tree/part7\" target=\"_blank\" rel=\"noopener noreferrer\">https://github.com/ByteAether/EnterpriseDal/tree/part7</a></strong></p><p>I hope this series has provided a valuable blueprint for your own advanced data access architectures.</p>",
      "date_published": "2025-12-01T00:00:00Z"
    }
    ,
    {
      "id": "https://byteaether.github.io/2025/building-an-enterprise-data-access-layer-composable-row-level-security/",
      "url": "https://byteaether.github.io/2025/building-an-enterprise-data-access-layer-composable-row-level-security/",
      "title": "Building an Enterprise Data Access Layer: Composable Row-Level Security",
      "content_html": "<p>Welcome to the sixth post in <a href=\"https://byteaether.github.io/series/enterprise-dal/\">our series on building a feature-rich, automated enterprise Data Access Layer (DAL)</a> using C# and <a href=\"https://github.com/linq2db/linq2db\" target=\"_blank\" rel=\"noopener noreferrer\">LinqToDB</a>. In our previous articles, we established a powerful architectural pattern: <a href=\"https://byteaether.github.io/2025/building-an-enterprise-data-access-layer-automated-soft-delete/\">a composable global query filter system</a>. This system allows us to define cross-cutting concerns as simple interfaces, automatically combining and applying them to every relevant query.</p><p>So far, we have successfully implemented:</p><ul><li><strong><a href=\"https://byteaether.github.io/2025/building-an-enterprise-data-access-layer-automated-auditing/\">Automated Auditing</a></strong> (<code>ICreatable</code>, <code>IModifiable</code>) to set <code>CreatedAt</code> and <code>ModifiedAt</code> timestamps.</li><li><strong><a href=\"https://byteaether.github.io/2025/building-an-enterprise-data-access-layer-automated-soft-delete/\">Transparent Soft-Deletes</a></strong> (<code>IRemovable</code>) to filter out records where <code>RemovedAt</code> is not <code>NULL</code>.</li><li><strong><a href=\"https://byteaether.github.io/2025/building-an-enterprise-data-access-layer-composable-multi-tenancy-filtering/\">Composable Multi-Tenancy</a></strong> (<code>ITenanted</code>) to automatically isolate data based on a <code>TenantId</code>, even through projected relationships.</li></ul><p>In this post, we will tackle the final and most complex pillar of our automated DAL: <strong>entity-based row-level security (RLS)</strong>. Our goal is to automatically filter any query to return only the records that the <em>current user</em> is authorized to access. We will achieve this by extending the exact same composable filter architecture we've already built, demonstrating its power and flexibility.</p><h2 id=\"1.-defining-the-security-contracts\" tabindex=\"-1\">1. Defining the Security Contracts</h2><p>To build an RLS system, we need to define three core components:</p><ol><li>A contract for the <strong>current user</strong>.</li><li>A contract for a <strong>permission record</strong>.</li><li>A contract for an entity that <strong>requires protection</strong>.</li></ol><h3 id=\"updating-the-database-context\" tabindex=\"-1\">Updating the Database Context</h3><p>First, we must make our DAL aware of the &quot;current user.&quot; We <a href=\"https://byteaether.github.io/2025/building-an-enterprise-data-access-layer-composable-multi-tenancy-filtering/\">previously</a> extended our <code>IDbCtx</code> with <code>DbCtxAttributes</code> to hold the current <code>TenantId</code>. We will now update that same record to also hold a <code>UserId</code>.</p><pre class=\"language-csharp\"><code class=\"language-csharp\"><span class=\"token comment\">// In DAL.Base/IDbCtx.cs</span>\n<span class=\"token keyword\">public</span> <span class=\"token keyword\">interface</span> <span class=\"token class-name\">IDbCtx</span> <span class=\"token punctuation\">:</span> <span class=\"token type-list\"><span class=\"token class-name\">IDataContext</span></span>\n<span class=\"token punctuation\">{</span>\n\t<span class=\"token return-type class-name\">DbCtxAttributes</span> Attributes <span class=\"token punctuation\">{</span> <span class=\"token keyword\">get</span><span class=\"token punctuation\">;</span> <span class=\"token keyword\">set</span><span class=\"token punctuation\">;</span> <span class=\"token punctuation\">}</span>\n\n\t<span class=\"token return-type class-name\">ITable<span class=\"token punctuation\">&lt;</span>IPermissionEntity<span class=\"token punctuation\">></span></span> <span class=\"token function\">GetPermissions</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span> <span class=\"token comment\">// New!</span>\n\n\t<span class=\"token keyword\">public</span> <span class=\"token keyword\">record</span> <span class=\"token class-name\">DbCtxAttributes</span><span class=\"token punctuation\">(</span>\n\t\t<span class=\"token class-name\">Ulid</span> TenantId <span class=\"token operator\">=</span> <span class=\"token keyword\">default</span><span class=\"token punctuation\">,</span>\n\t\t<span class=\"token class-name\">Ulid<span class=\"token punctuation\">?</span></span> UserId <span class=\"token operator\">=</span> <span class=\"token keyword\">null</span> <span class=\"token comment\">// New!</span>\n\t<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span></code></pre><p>We've added two things:</p><ul><li><code>Ulid? UserId</code>: This will be set by the application (e.g., from an HTTP context) at the beginning of a request. It's nullable, which is a critical design choice. If <code>UserId</code> is <code>null</code>, our RLS system will treat the user as unauthenticated and (by default) deny all access to protected entities.</li><li><code>ITable&lt;IPermissionEntity&gt; GetPermissions()</code>: This new method is an abstraction. Our filter logic needs to query &quot;permissions,&quot; but it shouldn't be hard-coded to a specific <code>Permission</code> table. This interface-based approach allows our <code>DbCtx</code> implementation to provide the correct table, promoting loose coupling.</li></ul><h3 id=\"the-permission-contract-(ipermissionentity)\" tabindex=\"-1\">The Permission Contract (<code>IPermissionEntity</code>)</h3><p>Next, we need a way to represent a permission. In our database, we've added a new <code>permission</code> table with a simple schema: <code>subject_id</code> (who has the permission) and <code>object_id</code> (what entity the permission is for).</p><p>To map this in code, we create a new interface:</p><pre class=\"language-csharp\"><code class=\"language-csharp\"><span class=\"token comment\">// In DAL.Base/EntityBehavior/IPermissionEntity.cs</span>\n<span class=\"token keyword\">public</span> <span class=\"token keyword\">interface</span> <span class=\"token class-name\">IPermissionEntity</span>\n<span class=\"token punctuation\">{</span>\n\t<span class=\"token return-type class-name\">Ulid</span> SubjectId <span class=\"token punctuation\">{</span> <span class=\"token keyword\">get</span><span class=\"token punctuation\">;</span> <span class=\"token punctuation\">}</span> <span class=\"token comment\">// The \"Who\" (e.g., a UserId)</span>\n\t<span class=\"token return-type class-name\">Ulid</span> ObjectId <span class=\"token punctuation\">{</span> <span class=\"token keyword\">get</span><span class=\"token punctuation\">;</span> <span class=\"token punctuation\">}</span>  <span class=\"token comment\">// The \"What\" (e.g., a PostId)</span>\n<span class=\"token punctuation\">}</span></code></pre><p>After running our scaffolder to generate the <code>Permission</code> entity from the new table, we simply create a partial class to apply this interface:</p><pre class=\"language-csharp\"><code class=\"language-csharp\"><span class=\"token comment\">// In DAL.Context/Entity/Permission.cs</span>\n<span class=\"token keyword\">public</span> <span class=\"token keyword\">partial</span> <span class=\"token keyword\">class</span> <span class=\"token class-name\">Permission</span> <span class=\"token punctuation\">:</span> <span class=\"token type-list\"><span class=\"token class-name\">IPermissionEntity</span></span>\n<span class=\"token punctuation\">{</span> <span class=\"token punctuation\">}</span></code></pre><p>Finally, we implement the new <code>GetPermissions()</code> method in our <code>DbCtx.cs</code> file. It's a simple one-line pass-through:</p><pre class=\"language-csharp\"><code class=\"language-csharp\"><span class=\"token comment\">// In DAL.Context/DbCtx.cs</span>\n<span class=\"token keyword\">public</span> <span class=\"token return-type class-name\">ITable<span class=\"token punctuation\">&lt;</span>IPermissionEntity<span class=\"token punctuation\">></span></span> <span class=\"token function\">GetPermissions</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">=></span> <span class=\"token generic-method\"><span class=\"token function\">GetTable</span><span class=\"token generic class-name\"><span class=\"token punctuation\">&lt;</span>Permission<span class=\"token punctuation\">></span></span></span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span></code></pre><h2 id=\"2.-the-iprotected-interface%3A-the-rls-filter\" tabindex=\"-1\">2. The <code>IProtected</code> Interface: The RLS Filter</h2><p>With our contracts in place, we can now create the &quot;behavior&quot; interface that will automatically apply our RLS filter. We'll call it <code>IProtected</code>. This interface is the heart of our new system and leverages the same <code>[EntityFilter]</code> attribute we used for soft-deletes and tenancy.</p><pre class=\"language-csharp\"><code class=\"language-csharp\"><span class=\"token comment\">// In DAL.Base/EntityBehavior/IProtected.cs</span>\n<span class=\"token punctuation\">[</span><span class=\"token generic-method\"><span class=\"token function\">EntityFilter</span><span class=\"token generic class-name\"><span class=\"token punctuation\">&lt;</span>IProtected<span class=\"token punctuation\">></span></span></span><span class=\"token punctuation\">(</span><span class=\"token keyword\">nameof</span><span class=\"token punctuation\">(</span>Filter<span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">]</span>\n<span class=\"token keyword\">public</span> <span class=\"token keyword\">interface</span> <span class=\"token class-name\">IProtected</span>\n<span class=\"token punctuation\">{</span>\n\t<span class=\"token return-type class-name\">Ulid</span> <span class=\"token function\">GetPermissionObjectId</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\n\t<span class=\"token keyword\">private</span> <span class=\"token keyword\">static</span> <span class=\"token return-type class-name\">IQueryable<span class=\"token punctuation\">&lt;</span>T<span class=\"token punctuation\">></span></span> <span class=\"token generic-method\"><span class=\"token function\">Filter</span><span class=\"token generic class-name\"><span class=\"token punctuation\">&lt;</span>T<span class=\"token punctuation\">></span></span></span><span class=\"token punctuation\">(</span><span class=\"token class-name\">IQueryable<span class=\"token punctuation\">&lt;</span>T<span class=\"token punctuation\">></span></span> q<span class=\"token punctuation\">,</span> <span class=\"token class-name\">IDbCtx</span> dbCtx<span class=\"token punctuation\">)</span>\n\t\t<span class=\"token keyword\">where</span> <span class=\"token class-name\">T</span> <span class=\"token punctuation\">:</span> <span class=\"token type-list\"><span class=\"token class-name\">IProtected</span></span>\n\t\t<span class=\"token operator\">=></span> dbCtx<span class=\"token punctuation\">.</span>Attributes<span class=\"token punctuation\">.</span>UserId <span class=\"token operator\">==</span> <span class=\"token keyword\">null</span>\n\t\t\t<span class=\"token comment\">// 1. If no user is in context, return nothing.</span>\n\t\t\t<span class=\"token punctuation\">?</span> q<span class=\"token punctuation\">.</span><span class=\"token function\">Where</span><span class=\"token punctuation\">(</span>_ <span class=\"token operator\">=></span> <span class=\"token boolean\">false</span><span class=\"token punctuation\">)</span>\n\t\t\t<span class=\"token comment\">// 2. Otherwise, join with permissions and filter.</span>\n\t\t\t<span class=\"token punctuation\">:</span> q<span class=\"token punctuation\">.</span><span class=\"token function\">InnerJoin</span><span class=\"token punctuation\">(</span>\n\t\t\t\tdbCtx<span class=\"token punctuation\">.</span><span class=\"token function\">GetPermissions</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">,</span>\n\t\t\t\t<span class=\"token punctuation\">(</span>entity<span class=\"token punctuation\">,</span> perm<span class=\"token punctuation\">)</span> <span class=\"token operator\">=></span>\n\t\t\t\t\tperm<span class=\"token punctuation\">.</span>ObjectId <span class=\"token operator\">==</span> entity<span class=\"token punctuation\">.</span><span class=\"token function\">GetPermissionObjectId</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">&amp;&amp;</span>\n\t\t\t\t\tperm<span class=\"token punctuation\">.</span>SubjectId <span class=\"token operator\">==</span> dbCtx<span class=\"token punctuation\">.</span>Attributes<span class=\"token punctuation\">.</span>UserId<span class=\"token punctuation\">,</span>\n\t\t\t\t<span class=\"token punctuation\">(</span>entity<span class=\"token punctuation\">,</span> perm<span class=\"token punctuation\">)</span> <span class=\"token operator\">=></span> entity\n\t\t\t<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span></code></pre><p>Let's break this down, as it's the most important piece:</p><ol><li><code>[EntityFilter&lt;...&gt;]</code>: This attribute, as before, tells our DAL's bootstrapper to find the <code>Filter</code> method and apply it to any entity that implements <code>IProtected</code>.</li><li><code>Ulid GetPermissionObjectId()</code>: This is the contract. Any entity implementing <code>IProtected</code> <em>must</em> tell the system which <code>ObjectId</code> to use for its permission check. This is a method, not a property, which allows for incredible flexibility, as we'll see next.</li><li><code>private static IQueryable&lt;T&gt; Filter&lt;...&gt;</code>: This is the filter logic.<ul><li><strong>The Security Check:</strong> First, it checks <code>dbCtx.Attributes.UserId == null</code>. If true, it returns <code>q.Where(_ =&gt; false)</code>. This is a &quot;fail-closed&quot; security principle. If we don't know who the user is, they get to see <em>nothing</em>.</li><li><strong>The RLS Join:</strong> If a user <em>is</em> present, it performs an <code>InnerJoin</code> between the entity query (<code>q</code>) and the permissions table (<code>dbCtx.GetPermissions()</code>).</li><li><strong>The Join Condition:</strong> This is the core logic: <code>perm.ObjectId == entity.GetPermissionObjectId() &amp;&amp; perm.SubjectId == dbCtx.Attributes.UserId</code>. It translates to: &quot;Find a permission record where the <code>ObjectId</code> matches the ID the entity provides, AND the <code>SubjectId</code> matches the current user in the context.&quot;</li><li><strong>The Result:</strong> The final lambda <code>(entity, perm) =&gt; entity</code> simply returns the original entity. The <code>InnerJoin</code> acts as a pure filter, discarding any <code>entity</code> that doesn't have a matching permission record for the current user.</li></ul></li></ol><h2 id=\"3.-implementation%3A-projected-permissions\" tabindex=\"-1\">3. Implementation: Projected Permissions</h2><p>Now, let's apply our new <code>IProtected</code> interface to an entity. A common scenario is that permissions aren't granted to granular entities like <code>Comment</code>, but rather to their parent, like <code>Post</code>. A user who can access a <code>Post</code> can access all of its <code>Comment</code>s.</p><p>Our <code>IProtected</code> interface's <code>GetPermissionObjectId()</code> method is designed for precisely this. We can implement it on our <code>Comment</code> entity to return its parent <code>Post.Id</code>. This is <a href=\"https://byteaether.github.io/2025/building-an-enterprise-data-access-layer-composable-multi-tenancy-filtering/\">the <em>exact</em> same &quot;projected&quot; logic we used for multi-tenancy</a>.</p><pre class=\"language-csharp\"><code class=\"language-csharp\"><span class=\"token comment\">// In DAL.Context/Entity/Comment.cs</span>\n<span class=\"token keyword\">public</span> <span class=\"token keyword\">partial</span> <span class=\"token keyword\">class</span> <span class=\"token class-name\">Comment</span> <span class=\"token punctuation\">:</span> <span class=\"token type-list\"><span class=\"token class-name\">IProtected</span></span>\n<span class=\"token punctuation\">{</span>\n\t<span class=\"token comment\">// This is the implementation of IProtected.GetPermissionObjectId()</span>\n\t<span class=\"token punctuation\">[</span><span class=\"token function\">ExpressionMethod</span><span class=\"token punctuation\">(</span><span class=\"token keyword\">nameof</span><span class=\"token punctuation\">(</span>GetPermissionObjectIdExpression<span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">]</span>\n\t<span class=\"token keyword\">public</span> <span class=\"token return-type class-name\">Ulid</span> <span class=\"token function\">GetPermissionObjectId</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">=></span> Post<span class=\"token punctuation\">.</span>Id<span class=\"token punctuation\">;</span>\n\n\t<span class=\"token keyword\">private</span> <span class=\"token keyword\">static</span> <span class=\"token return-type class-name\">Expression<span class=\"token punctuation\">&lt;</span>Func<span class=\"token punctuation\">&lt;</span>Comment<span class=\"token punctuation\">,</span> Ulid<span class=\"token punctuation\">></span><span class=\"token punctuation\">></span></span> <span class=\"token function\">GetPermissionObjectIdExpression</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span>\n\t\t<span class=\"token operator\">=></span> x <span class=\"token operator\">=></span> x<span class=\"token punctuation\">.</span>Post<span class=\"token punctuation\">.</span>Id<span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span></code></pre><p>By using LinqToDB's <code>[ExpressionMethod]</code>, we are telling the query engine that when it sees <code>entity.GetPermissionObjectId()</code> inside our <code>Filter</code>'s <code>InnerJoin</code>, it should substitute the expression <code>x =&gt; x.Post.Id</code>.</p><p>The RLS filter logic will now transparently translate to: <code>perm.ObjectId == comment.Post.Id</code>. The DAL will automatically join the <code>Comment</code> table to the <code>Post</code> table to get the <code>Id</code> for the check, all without the developer ever thinking about it.</p><h2 id=\"4.-the-payoff%3A-the-final-composed-query\" tabindex=\"-1\">4. The Payoff: The Final Composed Query</h2><p>We have now layered four distinct, automated behaviors onto our entities:</p><ol><li><code>ICreatable</code> &amp; <code>IModifiable</code> (Auditing)</li><li><code>IRemovable</code> (Soft-Delete)</li><li><code>ITenanted</code> (Multi-Tenancy)</li><li><code>IProtected</code> (Row-Level Security)</li></ol><p>Let's see what happens when we execute a simple business operation, like modifying a comment. We add this code to our <code>Program.cs</code>:</p><pre class=\"language-csharp\"><code class=\"language-csharp\"><span class=\"token comment\">// Set DB Context parameters</span>\nctx<span class=\"token punctuation\">.</span>Attributes <span class=\"token operator\">=</span> <span class=\"token keyword\">new</span><span class=\"token punctuation\">(</span>\n\ttenant<span class=\"token punctuation\">.</span>Id<span class=\"token punctuation\">,</span>\n\tu<span class=\"token punctuation\">.</span>Id\n<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\n<span class=\"token comment\">// ...</span>\n\n<span class=\"token comment\">// Try to update a protected comment entity</span>\n<span class=\"token keyword\">await</span> ctx<span class=\"token punctuation\">.</span><span class=\"token generic-method\"><span class=\"token function\">GetTable</span><span class=\"token generic class-name\"><span class=\"token punctuation\">&lt;</span>Comment<span class=\"token punctuation\">></span></span></span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span>\n\t<span class=\"token punctuation\">.</span><span class=\"token function\">Set</span><span class=\"token punctuation\">(</span>x <span class=\"token operator\">=></span> x<span class=\"token punctuation\">.</span>Content<span class=\"token punctuation\">,</span> <span class=\"token string\">\"asd123\"</span><span class=\"token punctuation\">)</span>\n\t<span class=\"token punctuation\">.</span><span class=\"token function\">ModifyAsync</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span></code></pre><p>Remember, our custom <code>ModifyAsync</code> extension also automatically handles setting the <code>ModifiedAt</code> timestamp. The developer wrote a simple, two-line update. Our DAL, however, composes <em>all</em> the rules and generates the following SQL:</p><pre class=\"language-sql\"><code class=\"language-sql\"><span class=\"token keyword\">UPDATE</span>\n\t<span class=\"token punctuation\">[</span><span class=\"token keyword\">comment</span><span class=\"token punctuation\">]</span>\n<span class=\"token keyword\">SET</span>\n\t<span class=\"token punctuation\">[</span>content<span class=\"token punctuation\">]</span> <span class=\"token operator\">=</span> <span class=\"token string\">'asd123'</span><span class=\"token punctuation\">,</span>\n\t<span class=\"token comment\">-- 1. \"ModifyAsync\" Auditing</span>\n\t<span class=\"token punctuation\">[</span>modified_at<span class=\"token punctuation\">]</span> <span class=\"token operator\">=</span> <span class=\"token string\">'2025-11-04 12:34:11.899'</span>\n<span class=\"token keyword\">FROM</span>\n\t<span class=\"token punctuation\">[</span>post<span class=\"token punctuation\">]</span> <span class=\"token punctuation\">[</span>a_Post<span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span>\n\t<span class=\"token punctuation\">[</span><span class=\"token keyword\">user</span><span class=\"token punctuation\">]</span> <span class=\"token punctuation\">[</span>a_User<span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span>\n\t<span class=\"token comment\">-- 4. RLS Join</span>\n\t<span class=\"token punctuation\">[</span>permission<span class=\"token punctuation\">]</span> <span class=\"token punctuation\">[</span>b<span class=\"token punctuation\">]</span>\n<span class=\"token keyword\">WHERE</span>\n\t<span class=\"token comment\">-- 2. Soft-Delete Filters (on all joined tables)</span>\n\t<span class=\"token punctuation\">[</span>a_User<span class=\"token punctuation\">]</span><span class=\"token punctuation\">.</span><span class=\"token punctuation\">[</span>removed_at<span class=\"token punctuation\">]</span> <span class=\"token operator\">IS</span> <span class=\"token boolean\">NULL</span> <span class=\"token operator\">AND</span>\n\t<span class=\"token punctuation\">[</span>a_Post<span class=\"token punctuation\">]</span><span class=\"token punctuation\">.</span><span class=\"token punctuation\">[</span>removed_at<span class=\"token punctuation\">]</span> <span class=\"token operator\">IS</span> <span class=\"token boolean\">NULL</span> <span class=\"token operator\">AND</span>\n\t<span class=\"token punctuation\">[</span><span class=\"token keyword\">comment</span><span class=\"token punctuation\">]</span><span class=\"token punctuation\">.</span><span class=\"token punctuation\">[</span>removed_at<span class=\"token punctuation\">]</span> <span class=\"token operator\">IS</span> <span class=\"token boolean\">NULL</span> <span class=\"token operator\">AND</span>\n\t<span class=\"token comment\">-- 3. Projected Multi-Tenancy Filter</span>\n\t<span class=\"token punctuation\">[</span>a_User<span class=\"token punctuation\">]</span><span class=\"token punctuation\">.</span><span class=\"token punctuation\">[</span>tenant_id<span class=\"token punctuation\">]</span> <span class=\"token operator\">=</span> X<span class=\"token string\">'019A4EDC4AD87A53C22679FA88F15885'</span> <span class=\"token operator\">AND</span>\n\t<span class=\"token comment\">-- Joins for projections</span>\n\t<span class=\"token punctuation\">[</span><span class=\"token keyword\">comment</span><span class=\"token punctuation\">]</span><span class=\"token punctuation\">.</span><span class=\"token punctuation\">[</span>post_id<span class=\"token punctuation\">]</span> <span class=\"token operator\">=</span> <span class=\"token punctuation\">[</span>a_Post<span class=\"token punctuation\">]</span><span class=\"token punctuation\">.</span><span class=\"token punctuation\">[</span>id<span class=\"token punctuation\">]</span> <span class=\"token operator\">AND</span>\n\t<span class=\"token punctuation\">[</span>a_Post<span class=\"token punctuation\">]</span><span class=\"token punctuation\">.</span><span class=\"token punctuation\">[</span>user_id<span class=\"token punctuation\">]</span> <span class=\"token operator\">=</span> <span class=\"token punctuation\">[</span>a_User<span class=\"token punctuation\">]</span><span class=\"token punctuation\">.</span><span class=\"token punctuation\">[</span>id<span class=\"token punctuation\">]</span> <span class=\"token operator\">AND</span>\n\t<span class=\"token comment\">-- 4. Projected Row-Level Security Filter</span>\n\t<span class=\"token punctuation\">[</span>b<span class=\"token punctuation\">]</span><span class=\"token punctuation\">.</span><span class=\"token punctuation\">[</span>object_id<span class=\"token punctuation\">]</span> <span class=\"token operator\">=</span> <span class=\"token punctuation\">[</span>a_Post<span class=\"token punctuation\">]</span><span class=\"token punctuation\">.</span><span class=\"token punctuation\">[</span>id<span class=\"token punctuation\">]</span> <span class=\"token operator\">AND</span>\n\t<span class=\"token punctuation\">[</span>b<span class=\"token punctuation\">]</span><span class=\"token punctuation\">.</span><span class=\"token punctuation\">[</span>subject_id<span class=\"token punctuation\">]</span> <span class=\"token operator\">=</span> X<span class=\"token string\">'019A4EDC4ADE42CA89BA742103A7A723'</span></code></pre><p>This query is a perfect demonstration of our architecture's power:</p><ol><li><strong>Auditing:</strong> <code>ModifyAsync</code> correctly injected the <code>modified_at</code> SET clause.</li><li><strong>Soft-Delete:</strong> The <code>[removed_at] IS NULL</code> filter was applied to <code>comment</code>, <code>post</code>, AND <code>user</code>.</li><li><strong>Multi-Tenancy:</strong> The projected tenancy filter (<code>[a_User].[tenant_id] = ...</code>) is present, ensuring we don't even <em>see</em> comments from other tenants.</li><li><strong>Row-Level Security:</strong> The new <code>IProtected</code> filter joined the <code>permission</code> table (<code>[b]</code>) and correctly filtered on the <em>Post ID</em> (<code>[b].[object_id] = [a_Post].[id]</code>) and the current <em>User ID</em> (<code>[b].[subject_id] = ...</code>).</li></ol><p>The developer is completely abstracted from this complexity. They just write business logic, and the DAL guarantees that auditing, soft-deletes, tenancy, and row-level security are <em>all</em> correctly and automatically enforced.</p><h2 id=\"conclusion\" tabindex=\"-1\">Conclusion</h2><p>With the addition of composable row-level security, our enterprise DAL framework is now feature-complete on its core data integrity and security goals. We have proven that a thoughtful architecture built on interfaces, expression trees, and composable filters can eliminate entire classes of common bugs and security vulnerabilities.</p><p>By separating the <em>definition</em> of a behavior (the interface) from its <em>implementation</em> (the filter logic), we've created a system that is robust, testable, and incredibly easy to extend.</p><p>As always, the complete, working implementation for this post is available in our GitHub repository: <strong><a href=\"https://github.com/ByteAether/EnterpriseDal/tree/part6\" target=\"_blank\" rel=\"noopener noreferrer\">https://github.com/ByteAether/EnterpriseDal/tree/part6</a></strong></p><p>In our next post, we will address the final piece of our auditing system: automatically populating the <code>CreatedBy</code> and <code>ModifiedBy</code> fields, leveraging the new <code>UserId</code> we just added to our <code>DbCtxAttributes</code>.</p>",
      "date_published": "2025-11-24T00:00:00Z"
    }
    ,
    {
      "id": "https://byteaether.github.io/2025/announcing-byteaetherulid-132-net-10-support-and-optimized-design/",
      "url": "https://byteaether.github.io/2025/announcing-byteaetherulid-132-net-10-support-and-optimized-design/",
      "title": "Announcing ByteAether.Ulid 1.3.2: .NET 10 Support and Optimized Design",
      "content_html": "<p>We are excited to announce the release of <strong>ByteAether.Ulid version 1.3.2</strong>, which is now available on <a href=\"https://www.nuget.org/packages/ByteAether.Ulid/\" target=\"_blank\" rel=\"noopener noreferrer\">NuGet</a>. This release introduces official support and dedicated binaries for the new <strong>.NET 10</strong> platform, continuing our mission to provide the most performant, secure, and specification-compliant <a href=\"https://github.com/ulid/spec\" target=\"_blank\" rel=\"noopener noreferrer\">ULID (Universally Unique Lexicographically Sortable Identifier)</a> implementation for the .NET ecosystem.</p><p>For those new to the project, <a href=\"https://github.com/ByteAether/Ulid\" target=\"_blank\" rel=\"noopener noreferrer\">ByteAether.Ulid</a> provides a robust solution engineered to solve critical, often-overlooked issues in unique identifier generation, such as reliable overflow handling and secure monotonic generation.</p><h2 id=\"embracing-.net-10-and-finalizing-c%23-14-features\" tabindex=\"-1\">Embracing .NET 10 and Finalizing C# 14 Features</h2><p>The primary feature of version 1.3.2 is the inclusion of build artifacts specifically compiled against the <strong>.NET 10 SDK</strong>.</p><p>This release also solidifies our use of modern C# features. We were an early adopter of C# 14's &quot;preview&quot; features, specifically the <code>field</code> keyword. With the official release of .NET 10, we can now move from <code>LangVersion</code> &quot;preview&quot; to &quot;latest,&quot; locking in the benefits this feature provides.</p><p>We use this feature in our <code>GenerationOptions</code> record to perform &quot;fail-fast&quot; validation. By validating a property <em>once</em> during its <code>init</code> assignment, we avoid all validation overhead on subsequent <code>get</code> operations or, more importantly, inside the <code>Ulid.New()</code> hot path.</p><p>For example, here is how we ensure that the <code>Monotonicity</code> property is always set to a valid <code>enum</code> value:</p><pre class=\"language-csharp\"><code class=\"language-csharp\"><span class=\"token keyword\">public</span> <span class=\"token return-type class-name\">MonotonicityOptions</span> Monotonicity\n<span class=\"token punctuation\">{</span>\n\t<span class=\"token keyword\">get</span><span class=\"token punctuation\">;</span>\n\tinit <span class=\"token operator\">=></span> field <span class=\"token operator\">=</span> Enum<span class=\"token punctuation\">.</span><span class=\"token function\">IsDefined</span><span class=\"token punctuation\">(</span><span class=\"token keyword\">typeof</span><span class=\"token punctuation\">(</span><span class=\"token type-expression class-name\">MonotonicityOptions</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">,</span> <span class=\"token keyword\">value</span><span class=\"token punctuation\">)</span>\n\t\t<span class=\"token punctuation\">?</span> <span class=\"token keyword\">value</span>\n\t\t<span class=\"token punctuation\">:</span> <span class=\"token keyword\">throw</span> <span class=\"token keyword\">new</span> <span class=\"token constructor-invocation class-name\">ArgumentOutOfRangeException</span><span class=\"token punctuation\">(</span>\n\t\t\t<span class=\"token keyword\">nameof</span><span class=\"token punctuation\">(</span>Monotonicity<span class=\"token punctuation\">)</span><span class=\"token punctuation\">,</span>\n\t\t\t<span class=\"token keyword\">value</span><span class=\"token punctuation\">,</span>\n\t\t\t<span class=\"token string\">\"Invalid monotonicity option.\"</span>\n\t\t<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span> <span class=\"token operator\">=</span> MonotonicityOptions<span class=\"token punctuation\">.</span>MonotonicIncrement<span class=\"token punctuation\">;</span></code></pre><p>This pattern provides a critical optimization. The validation logic runs a single time when the <code>GenerationOptions</code> object is constructed, ensuring that any call to <code>Ulid.New()</code> using these options operates with pre-validated, zero-overhead configuration.</p><h2 id=\"a-deeper-look%3A-our-multi-targeting-strategy\" tabindex=\"-1\">A Deeper Look: Our Multi-Targeting Strategy</h2><p>Our NuGet package ships distinct, optimized artifacts for a wide range of targets:</p><ul><li><strong>.NET 10</strong></li><li><strong>.NET 9</strong></li><li><strong>.NET 8</strong></li><li><strong>.NET 7</strong></li><li><strong>.NET 6</strong></li><li><strong>.NET 5</strong></li><li><strong>.NET Standard 2.1</strong></li><li><strong>.NET Standard 2.0</strong></li></ul><p>This multi-targeting strategy is fundamental to our performance promise. We avoid the &quot;lowest common denominator&quot; approach of only shipping a <code>.NET Standard 2.0</code> assembly.</p><p>When you install <code>ByteAether.Ulid</code> in a .NET 10 project, NuGet automatically selects the <code>.NET 10</code> binary. This binary is compiled with all the latest JIT intrinsics and BCL improvements. Our codebase is rich with conditional compilation, allowing us to use the most powerful APIs available on each platform.</p><p>For example, our parsing (<code>Ulid.Parse()</code>) and stringification (<code>ulid.ToString()</code>) routines use optimized <code>ReadOnlySpan&lt;T&gt;</code> and <code>MemoryMarshal</code> APIs on modern targets, often leveraging SIMD for Base32 operations. This is a primary reason for our benchmark-leading performance, and it's an advantage you would lose if you only consumed a <code>.NET Standard 2.0</code>-compiled library.</p><h2 id=\"how-this-philosophy-shapes-our-core-features\" tabindex=\"-1\">How This Philosophy Shapes Our Core Features</h2><p>This focus on performance and robust design is directly applied to the library's core features, particularly in how we handle security and reliability.</p><h3 id=\"reliable-overflow-handling\" tabindex=\"-1\">Reliable Overflow Handling</h3><p>A <a href=\"https://github.com/ulid/spec/issues/39\" target=\"_blank\" rel=\"noopener noreferrer\">critical, and often ignored, problem</a> in ULID generation is the <code>OverflowException</code> that can occur during rapid-fire generation (many IDs in the same millisecond). While the 80-bit random component is massive, its starting value is <em>random</em>. This means a newly generated ULID could, by chance, have a random component already near its maximum value.</p><p>In this scenario, even a few increments (especially multi-byte random increments) could cause it to overflow. Our library programmatically prevents this by intelligently incrementing the 48-bit timestamp component by 1ms when an overflow is imminent, ensuring continuous, error-free generation.</p><p>We've written a detailed analysis of this problem and our solution here: <strong><a href=\"https://byteaether.github.io/2025/prioritizing-reliability-when-milliseconds-arent-enough/\">Prioritizing Reliability: When Milliseconds Aren't Enough</a></strong></p><h3 id=\"secure-and-configurable-generation\" tabindex=\"-1\">Secure and Configurable Generation</h3><p>The <code>GenerationOptions</code> class, which benefits from the <code>field</code> keyword mentioned earlier, is the key to our advanced security and performance tuning.</p><p>It allows you to configure <code>MonotonicityOptions</code> (e.g., <code>MonotonicRandom1Byte</code>) to <a href=\"https://github.com/ulid/spec/issues/105\" target=\"_blank\" rel=\"noopener noreferrer\">defeat enumeration attacks, a vulnerability in simple <code>+1</code> incrementors</a>. It also lets you control the <code>IRandomProvider</code> used for both the initial random bytes and the monotonic increments. This design provides a nuanced balance, allowing for a fast, non-blocking pseudo-random incrementor while using a cryptographically secure generator for the initial seed.</p><p>You can read a deep dive into these options and their security implications here: <strong><a href=\"https://byteaether.github.io/2025/byteaetherulid-v130-enhanced-ulid-generation-control-and-security/\">ByteAether.Ulid v1.3.0: Enhanced ULID Generation Control and Security</a></strong></p><h2 id=\"conclusion\" tabindex=\"-1\">Conclusion</h2><p>The 1.3.2 release of <strong>ByteAether.Ulid</strong>, with its .NET 10 support, continues our commitment to providing a ULID implementation that is fast, secure, and meticulously spec-compliant.</p><p>Our aggressive multi-targeting strategy ensures your application uses the most optimized code for its platform, and our forward-looking adoption of C# features allows us to build a cleaner, faster, and more reliable API.</p><p>We encourage all architects and senior engineers who value performance, security, and specification-compliance to adopt ByteAether.Ulid for their identifier generation needs.</p><ul><li><strong>Get the package on NuGet:</strong> <a href=\"https://www.nuget.org/packages/ByteAether.Ulid/\" target=\"_blank\" rel=\"noopener noreferrer\">https://www.nuget.org/packages/ByteAether.Ulid/</a><pre class=\"language-sh\"><code class=\"language-sh\">dotnet <span class=\"token function\">add</span> package ByteAether.Ulid</code></pre></li><li><strong>Explore the code, read the full README, and contribute on GitHub:</strong> <a href=\"https://github.com/ByteAether/Ulid\" target=\"_blank\" rel=\"noopener noreferrer\">https://github.com/ByteAether/Ulid</a></li></ul>",
      "date_published": "2025-11-14T00:00:00Z"
    }
    ,
    {
      "id": "https://byteaether.github.io/2025/building-an-enterprise-data-access-layer-composable-multi-tenancy-filtering/",
      "url": "https://byteaether.github.io/2025/building-an-enterprise-data-access-layer-composable-multi-tenancy-filtering/",
      "title": "Building an Enterprise Data Access Layer: Composable Multi-Tenancy Filtering",
      "content_html": "<p>In our <a href=\"https://byteaether.github.io/series/enterprise-dal/\">previous articles</a>, we established a robust foundation for our enterprise Data Access Layer (DAL). We <a href=\"https://byteaether.github.io/2025/building-an-enterprise-data-access-layer-database-and-code-structure/\">began with a database-first approach</a> using C# and <a href=\"https://github.com/linq2db/linq2db\" target=\"_blank\" rel=\"noopener noreferrer\">LinqToDB</a>, then <a href=\"https://byteaether.github.io/2025/building-an-enterprise-data-access-layer-automated-auditing/\">implemented automated auditing</a> for <code>CreatedAt</code> and <code>ModifiedAt</code> timestamps. Most recently, <a href=\"https://byteaether.github.io/2025/building-an-enterprise-data-access-layer-automated-soft-delete/\">we engineered a powerful, composable global query filter system</a> to handle soft-deletes transparently. This architecture was designed for extensibility, and in this post, we will leverage that investment to tackle one of the most critical cross-cutting concerns in enterprise software: multi-tenancy.</p><p>Multi-tenancy ensures that one tenant's data is strictly isolated from another's. A failure in this area is not a minor bug, but a critical security breach. Our goal is to enforce this isolation at the lowest possible level—the DAL—to guarantee that it is impossible for business logic to accidentally query data from the wrong tenant.</p><p>We will extend our entity behavior model by introducing an <code>ITenanted</code> interface, update our database context to be tenant-aware, and enhance our scaffolding interceptor for automation. Crucially, we will also solve the complex problem of &quot;projected tenancy,&quot; where an entity's tenant affiliation is derived from a related entity.</p><p>The complete source code for this part is available on GitHub: <a href=\"https://github.com/ByteAether/EnterpriseDal/tree/part5\" target=\"_blank\" rel=\"noopener noreferrer\">https://github.com/ByteAether/EnterpriseDal/tree/part5</a>.</p><h2 id=\"1.-establishing-tenant-context-in-the-dal\" tabindex=\"-1\">1. Establishing Tenant Context in the DAL</h2><p>Before our DAL can filter by tenant, it needs to know the identity of the current tenant for any given operation. This context is typically derived from an HTTP request, a message queue header, or a similar scope. We need a clean mechanism to pass this information down to our database context.</p><p>Previously, our <code>IDbCtx</code> was a simple marker interface. We will now extend it to carry request-scoped attributes, starting with the <code>TenantId</code>.</p><p><strong>File: <code>DAL.Base/IDbCtx.cs</code></strong></p><pre class=\"language-csharp\"><code class=\"language-csharp\"><span class=\"token keyword\">namespace</span> <span class=\"token namespace\">DAL<span class=\"token punctuation\">.</span>Base</span><span class=\"token punctuation\">;</span>\n\n<span class=\"token keyword\">public</span> <span class=\"token keyword\">interface</span> <span class=\"token class-name\">IDbCtx</span> <span class=\"token punctuation\">:</span> <span class=\"token type-list\"><span class=\"token class-name\">IDataContext</span></span>\n<span class=\"token punctuation\">{</span>\n\t<span class=\"token return-type class-name\">DbCtxAttributes</span> Attributes <span class=\"token punctuation\">{</span> <span class=\"token keyword\">get</span><span class=\"token punctuation\">;</span> <span class=\"token keyword\">set</span><span class=\"token punctuation\">;</span> <span class=\"token punctuation\">}</span>\n\n\t<span class=\"token keyword\">public</span> <span class=\"token keyword\">record</span> <span class=\"token class-name\">DbCtxAttributes</span><span class=\"token punctuation\">(</span><span class=\"token class-name\">Ulid</span> TenantId <span class=\"token operator\">=</span> <span class=\"token keyword\">default</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span></code></pre><p>By adding the <code>Attributes</code> property, we provide a dedicated, extensible home for contextual data. We use a C# <code>record</code> for <code>DbCtxAttributes</code> to create a simple, immutable data carrier. The <code>IDbCtx</code> implementation (our <code>DbCtx</code> class) will be responsible for initializing this property. In a real application, this would be done via dependency injection at the beginning of a request scope, making the current <code>TenantId</code> available for the lifetime of that request.</p><p>For our demonstration, we will manually set this value in <code>Program.cs</code> before executing any queries. This simulates how a tenant's context would be established at the start of an operation.</p><p><strong>File: <code>App/Program.cs</code> (Addition)</strong></p><pre class=\"language-csharp\"><code class=\"language-csharp\"><span class=\"token class-name\"><span class=\"token keyword\">var</span></span> tenantId <span class=\"token operator\">=</span> Ulid<span class=\"token punctuation\">.</span><span class=\"token function\">New</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\nctx<span class=\"token punctuation\">.</span>Attributes <span class=\"token operator\">=</span> <span class=\"token keyword\">new</span><span class=\"token punctuation\">(</span><span class=\"token named-parameter punctuation\">TenantId</span><span class=\"token punctuation\">:</span> tenantId<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span></code></pre><p>While we use a new Ulid here, the key is that this specific value will later appear in the generated SQL <code>WHERE</code> clause, confirming that our filter is using the context we've provided.</p><h2 id=\"2.-defining-the-tenancy-contract%3A-the-itenanted-interface\" tabindex=\"-1\">2. Defining the Tenancy Contract: The <code>ITenanted</code> Interface</h2><p>Following the pattern established with <code>ICreatable</code> and <code>IRemovable</code>, we will define tenancy as an entity behavior through a new interface. This interface serves two purposes: it enforces a contract that the entity must expose a <code>TenantId</code>, and it hooks into our global filter system.</p><p><strong>File: <code>DAL.Base/EntityBehavior/ITenanted.cs</code></strong></p><pre class=\"language-csharp\"><code class=\"language-csharp\"><span class=\"token keyword\">namespace</span> <span class=\"token namespace\">DAL<span class=\"token punctuation\">.</span>Base<span class=\"token punctuation\">.</span>EntityBehavior</span><span class=\"token punctuation\">;</span>\n\n<span class=\"token punctuation\">[</span><span class=\"token generic-method\"><span class=\"token function\">EntityFilter</span><span class=\"token generic class-name\"><span class=\"token punctuation\">&lt;</span>ITenanted<span class=\"token punctuation\">></span></span></span><span class=\"token punctuation\">(</span><span class=\"token keyword\">nameof</span><span class=\"token punctuation\">(</span>Filter<span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">]</span>\n<span class=\"token keyword\">public</span> <span class=\"token keyword\">interface</span> <span class=\"token class-name\">ITenanted</span> <span class=\"token punctuation\">:</span> <span class=\"token type-list\"><span class=\"token class-name\">IEntity</span></span>\n<span class=\"token punctuation\">{</span>\n\t<span class=\"token return-type class-name\">Ulid</span> TenantId <span class=\"token punctuation\">{</span> <span class=\"token keyword\">get</span><span class=\"token punctuation\">;</span> <span class=\"token punctuation\">}</span>\n\n\t<span class=\"token keyword\">private</span> <span class=\"token keyword\">static</span> <span class=\"token return-type class-name\">IQueryable<span class=\"token punctuation\">&lt;</span>T<span class=\"token punctuation\">></span></span> <span class=\"token generic-method\"><span class=\"token function\">Filter</span><span class=\"token generic class-name\"><span class=\"token punctuation\">&lt;</span>T<span class=\"token punctuation\">></span></span></span><span class=\"token punctuation\">(</span><span class=\"token class-name\">IQueryable<span class=\"token punctuation\">&lt;</span>T<span class=\"token punctuation\">></span></span> q<span class=\"token punctuation\">,</span> <span class=\"token class-name\">IDbCtx</span> ctx<span class=\"token punctuation\">)</span> <span class=\"token keyword\">where</span> <span class=\"token class-name\">T</span> <span class=\"token punctuation\">:</span> <span class=\"token type-list\"><span class=\"token class-name\">ITenanted</span></span>\n\t\t<span class=\"token operator\">=></span> q<span class=\"token punctuation\">.</span><span class=\"token function\">Where</span><span class=\"token punctuation\">(</span>x <span class=\"token operator\">=></span> x<span class=\"token punctuation\">.</span>TenantId <span class=\"token operator\">==</span> ctx<span class=\"token punctuation\">.</span>Attributes<span class=\"token punctuation\">.</span>TenantId<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span></code></pre><p>This implementation is concise but powerful, directly leveraging our previous work:</p><ol><li><strong><code>Ulid TenantId { get; }</code></strong>: This property contract mandates that any class implementing <code>ITenanted</code> must provide a <code>TenantId</code> getter. This is the key piece of information needed for filtering.</li><li><strong><code>[EntityFilter&lt;ITenanted&gt;(nameof(Filter))]</code></strong>: This is the connection to our composable filter architecture. We are associating the <code>ITenanted</code> interface with a specific filter-providing method named <code>Filter</code>.</li><li><strong><code>private static IQueryable&lt;T&gt; Filter(...)</code></strong>: This method contains the filtering logic itself. It receives the current queryable (<code>q</code>) and the database context (<code>ctx</code>). It then appends a standard LINQ <code>Where</code> clause, comparing the entity's <code>TenantId</code> with the <code>TenantId</code> we stored in the context's <code>Attributes</code>.</li></ol><p>When the DAL is initialized, our <code>MappingSchema.ApplyEntityFilters&lt;DbCtx&gt;()</code> helper will discover this attribute and automatically apply this <code>Where</code> clause to every query against any entity that implements <code>ITenanted</code>.</p><h2 id=\"3.-automating-interface-scaffolding\" tabindex=\"-1\">3. Automating Interface Scaffolding</h2><p>To maintain our database-first philosophy, we must ensure that our generated entity classes automatically implement the <code>ITenanted</code> interface if their corresponding database tables contain a <code>tenant_id</code> column. This task is perfectly suited for our custom scaffolding interceptor.</p><p>We will add a simple check to the interceptor's logic.</p><p><strong>File: <code>DAL.ScaffoldInterceptor/Interceptor.cs</code> (Addition)</strong></p><pre class=\"language-csharp\"><code class=\"language-csharp\"><span class=\"token comment\">// ITenanted</span>\n<span class=\"token class-name\"><span class=\"token keyword\">var</span></span> tenantIdField <span class=\"token operator\">=</span> entityModel<span class=\"token punctuation\">.</span>Columns<span class=\"token punctuation\">.</span><span class=\"token function\">FirstOrDefault</span><span class=\"token punctuation\">(</span>x <span class=\"token operator\">=></span>\n\tx<span class=\"token punctuation\">.</span>Property<span class=\"token punctuation\">.</span>Name <span class=\"token operator\">==</span> <span class=\"token keyword\">nameof</span><span class=\"token punctuation\">(</span>ITenanted<span class=\"token punctuation\">.</span>TenantId<span class=\"token punctuation\">)</span>\n<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token keyword\">if</span> <span class=\"token punctuation\">(</span>tenantIdField <span class=\"token keyword\">is</span> <span class=\"token keyword\">not</span> <span class=\"token keyword\">null</span><span class=\"token punctuation\">)</span>\n<span class=\"token punctuation\">{</span>\n\taddedInterfaces<span class=\"token punctuation\">.</span><span class=\"token function\">Add</span><span class=\"token punctuation\">(</span><span class=\"token keyword\">typeof</span><span class=\"token punctuation\">(</span><span class=\"token type-expression class-name\">ITenanted</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span></code></pre><p>This code snippet inspects the columns of the entity being scaffolded. If it finds a column that maps to a property named <code>TenantId</code>, it adds <code>ITenanted</code> to the list of interfaces for the generated partial class. With this change, entities like <code>user</code> which have a direct <code>tenant_id</code> column will be automatically protected by our tenancy filter without any manual intervention.</p><h2 id=\"4.-the-advanced-case%3A-projected-tenancy\" tabindex=\"-1\">4. The Advanced Case: Projected Tenancy</h2><p>The true test of our architecture is not in handling direct foreign keys, but in its ability to manage more complex, indirect relationships. Consider the <code>post</code> table. Its schema does not contain a <code>tenant_id</code> column. However, a post is unequivocally owned by a tenant because it belongs to a <code>user</code>, and that <code>user</code> belongs to a <code>tenant</code>. The <code>Post</code> entity's tenancy is therefore <em>projected</em> from its parent <code>User</code> entity.</p><p>Our DAL must enforce this projected relationship as a filtering rule. We need to make the <code>Post</code> entity implement <code>ITenanted</code>, but how do we implement the <code>TenantId</code> property when there is no backing column?</p><p>The solution lies in creating an expression-based property that LinqToDB can translate directly into SQL. We achieve this by manually creating a partial class for <code>Post</code> and using the <a href=\"http://blog.linq2db.com/2016/06/how-to-teach-linq-to-db-convert-custom.html\" target=\"_blank\" rel=\"noopener noreferrer\"><code>[ExpressionMethod]</code></a> attribute.</p><p><strong>File: <code>DAL.Context/Entity/Post.cs</code></strong></p><pre class=\"language-csharp\"><code class=\"language-csharp\"><span class=\"token keyword\">namespace</span> <span class=\"token namespace\">DAL<span class=\"token punctuation\">.</span>Context<span class=\"token punctuation\">.</span>Entity</span><span class=\"token punctuation\">;</span>\n\n<span class=\"token keyword\">public</span> <span class=\"token keyword\">partial</span> <span class=\"token keyword\">class</span> <span class=\"token class-name\">Post</span> <span class=\"token punctuation\">:</span> <span class=\"token type-list\"><span class=\"token class-name\">ITenanted</span></span>\n<span class=\"token punctuation\">{</span>\n\t<span class=\"token punctuation\">[</span><span class=\"token attribute\"><span class=\"token class-name\">ExpressionMethod</span><span class=\"token attribute-arguments\"><span class=\"token punctuation\">(</span><span class=\"token keyword\">nameof</span><span class=\"token punctuation\">(</span>GetTenantIdExpression<span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span></span></span><span class=\"token punctuation\">]</span>\n\t<span class=\"token keyword\">public</span> <span class=\"token return-type class-name\">Ulid</span> TenantId <span class=\"token operator\">=></span> <span class=\"token function\">GetTenantIdExpression</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">.</span><span class=\"token function\">Compile</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">(</span><span class=\"token keyword\">this</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\n\t<span class=\"token keyword\">private</span> <span class=\"token keyword\">static</span> <span class=\"token return-type class-name\">Expression<span class=\"token punctuation\">&lt;</span>Func<span class=\"token punctuation\">&lt;</span>Post<span class=\"token punctuation\">,</span> Ulid<span class=\"token punctuation\">></span><span class=\"token punctuation\">></span></span> <span class=\"token function\">GetTenantIdExpression</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span>\n\t\t<span class=\"token operator\">=></span> x <span class=\"token operator\">=></span> x<span class=\"token punctuation\">.</span>User<span class=\"token punctuation\">.</span>TenantId<span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span></code></pre><p>Let's dissect this implementation, as it is central to the power of our DAL:</p><ol><li><strong><code>public partial class Post : ITenanted</code></strong>: We explicitly declare that <code>Post</code> conforms to the <code>ITenanted</code> contract. We do this manually because our scaffolder did not find a <code>TenantId</code> column.</li><li><strong><code>private static Expression&lt;Func&lt;Post, Ulid&gt;&gt; GetTenantIdExpression()</code></strong>: Instead of a simple value, we define the logic for retrieving the <code>TenantId</code> as an <em>expression tree</em>. The expression <code>x =&gt; x.User.TenantId</code> is not just executable C# code, but it is a data structure that represents the navigation from a <code>Post</code> (<code>x</code>), through its <code>User</code> property, to that user's <code>TenantId</code>. LinqToDB is designed to parse these expression trees and convert them into the corresponding SQL, which in this case involves an <code>INNER JOIN</code> to the <code>user</code> table.</li><li><strong><code>[ExpressionMethod(nameof(GetTenantIdExpression))]</code></strong>: This LinqToDB attribute is the magic that connects the property to the expression tree. When LinqToDB encounters <code>post.TenantId</code> inside a LINQ query (like the one in our <code>ITenanted.Filter</code> method), this attribute instructs it to substitute the SQL translation of the <code>GetTenantIdExpression</code> method's return value.</li><li><strong><code>public Ulid TenantId =&gt; GetTenantIdExpression().Compile()(this);</code></strong>: This line handles the case where the <code>TenantId</code> property is accessed on an already-materialized <code>Post</code> object in C# memory, outside of a database query. In this scenario, LinqToDB is not involved, and the <code>[ExpressionMethod]</code> attribute has no effect. We must provide a valid C# implementation. We take our expression tree, <code>.Compile()</code> it into a runnable delegate, and then invoke it for the current <code>Post</code> instance (<code>this</code>). For performance-critical code, this compiled delegate could be cached, but we have kept it simple for clarity.</li></ol><p>This elegant solution allows the <code>TenantId</code> property to work seamlessly both within database queries (translating to SQL) and on in-memory objects (executing as C# code).</p><h2 id=\"5.-observing-composable-filters-in-action\" tabindex=\"-1\">5. Observing Composable Filters in Action</h2><p>We have now implemented all the necessary components. The <code>ITenanted</code> interface defines the filter, the scaffolder applies it to entities with a direct <code>TenantId</code>, and we have manually implemented it for the <code>Post</code> entity using a projected property.</p><p>Let's verify that it all works together. We add a simple query to our application's entry point and inspect the generated SQL.</p><p><strong>File: <code>App/Program.cs</code> (Addition)</strong></p><pre class=\"language-csharp\"><code class=\"language-csharp\"><span class=\"token comment\">// Set DB Context parameters</span>\nctx<span class=\"token punctuation\">.</span>Attributes <span class=\"token operator\">=</span> <span class=\"token keyword\">new</span><span class=\"token punctuation\">(</span>\n\ttenant<span class=\"token punctuation\">.</span>Id\n<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\n<span class=\"token comment\">// ...</span>\n\n<span class=\"token comment\">// Get posts that are tenanted through a user</span>\n<span class=\"token keyword\">await</span> ctx<span class=\"token punctuation\">.</span><span class=\"token generic-method\"><span class=\"token function\">GetTable</span><span class=\"token generic class-name\"><span class=\"token punctuation\">&lt;</span>Post<span class=\"token punctuation\">></span></span></span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span>\n    <span class=\"token punctuation\">.</span><span class=\"token function\">ToListAsync</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\nConsole<span class=\"token punctuation\">.</span><span class=\"token function\">WriteLine</span><span class=\"token punctuation\">(</span>ctx<span class=\"token punctuation\">.</span>LastQuery<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span></code></pre><p>Executing this code produces the following SQL:</p><pre class=\"language-sql\"><code class=\"language-sql\"><span class=\"token keyword\">SELECT</span>\n    <span class=\"token punctuation\">[</span>x<span class=\"token punctuation\">]</span><span class=\"token punctuation\">.</span><span class=\"token punctuation\">[</span>id<span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span>\n    <span class=\"token punctuation\">[</span>x<span class=\"token punctuation\">]</span><span class=\"token punctuation\">.</span><span class=\"token punctuation\">[</span>user_id<span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span>\n    <span class=\"token punctuation\">[</span>x<span class=\"token punctuation\">]</span><span class=\"token punctuation\">.</span><span class=\"token punctuation\">[</span>title<span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span>\n    <span class=\"token punctuation\">[</span>x<span class=\"token punctuation\">]</span><span class=\"token punctuation\">.</span><span class=\"token punctuation\">[</span>content<span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span>\n    <span class=\"token punctuation\">[</span>x<span class=\"token punctuation\">]</span><span class=\"token punctuation\">.</span><span class=\"token punctuation\">[</span>created_at<span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span>\n    <span class=\"token punctuation\">[</span>x<span class=\"token punctuation\">]</span><span class=\"token punctuation\">.</span><span class=\"token punctuation\">[</span>modified_at<span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span>\n    <span class=\"token punctuation\">[</span>x<span class=\"token punctuation\">]</span><span class=\"token punctuation\">.</span><span class=\"token punctuation\">[</span>removed_at<span class=\"token punctuation\">]</span>\n<span class=\"token keyword\">FROM</span>\n    <span class=\"token punctuation\">[</span>post<span class=\"token punctuation\">]</span> <span class=\"token punctuation\">[</span>x<span class=\"token punctuation\">]</span>\n\t<span class=\"token keyword\">INNER</span> <span class=\"token keyword\">JOIN</span> <span class=\"token punctuation\">[</span><span class=\"token keyword\">user</span><span class=\"token punctuation\">]</span> <span class=\"token punctuation\">[</span>a_User<span class=\"token punctuation\">]</span> <span class=\"token keyword\">ON</span> <span class=\"token punctuation\">[</span>x<span class=\"token punctuation\">]</span><span class=\"token punctuation\">.</span><span class=\"token punctuation\">[</span>user_id<span class=\"token punctuation\">]</span> <span class=\"token operator\">=</span> <span class=\"token punctuation\">[</span>a_User<span class=\"token punctuation\">]</span><span class=\"token punctuation\">.</span><span class=\"token punctuation\">[</span>id<span class=\"token punctuation\">]</span>\n<span class=\"token keyword\">WHERE</span>\n    <span class=\"token punctuation\">[</span>a_User<span class=\"token punctuation\">]</span><span class=\"token punctuation\">.</span><span class=\"token punctuation\">[</span>removed_at<span class=\"token punctuation\">]</span> <span class=\"token operator\">IS</span> <span class=\"token boolean\">NULL</span>\n    <span class=\"token operator\">AND</span> <span class=\"token punctuation\">[</span>a_User<span class=\"token punctuation\">]</span><span class=\"token punctuation\">.</span><span class=\"token punctuation\">[</span>tenant_id<span class=\"token punctuation\">]</span> <span class=\"token operator\">=</span> X<span class=\"token string\">'0199DD7C56CCCB4521596E010C8BED67'</span>\n    <span class=\"token operator\">AND</span> <span class=\"token punctuation\">[</span>x<span class=\"token punctuation\">]</span><span class=\"token punctuation\">.</span><span class=\"token punctuation\">[</span>removed_at<span class=\"token punctuation\">]</span> <span class=\"token operator\">IS</span> <span class=\"token boolean\">NULL</span></code></pre><p>This output is a perfect confirmation of our architecture's capabilities. A simple call to <code>ToListAsync()</code> on the <code>Post</code> table has triggered a cascade of automated, composed filters:</p><ul><li><strong><code>INNER JOIN [user] [a_User] ...</code></strong>:<br/>LinqToDB generated this join automatically because the <code>ITenanted</code> filter on <code>Post</code> required access to <code>User.TenantId</code> via our <code>ExpressionMethod</code>.</li><li><strong><code>WHERE ... [a_User].[tenant_id] = ...</code></strong>:<br/>This is our new tenancy filter in action, correctly applied to the joined <code>user</code> table.</li><li><strong><code>WHERE ... [x].[removed_at] IS NULL</code></strong>:<br/>The soft-delete filter for the <code>Post</code> entity is present, as expected from our previous work.</li><li><strong><code>WHERE ... [a_User].[removed_at] IS NULL</code></strong>:<br/>The soft-delete filter for the <code>User</code> entity has <em>also</em> been applied to the joined table. Our system correctly aggregates filters for all entities involved in the query.</li></ul><p>This demonstrates the power of composable filters. Each rule (soft-delete, tenancy) is defined independently, yet the system correctly combines them into a single, comprehensive <code>WHERE</code> clause, ensuring data integrity and security automatically.</p><h2 id=\"conclusion\" tabindex=\"-1\">Conclusion</h2><p>In this article, we successfully implemented automated, non-bypassable multi-tenancy filtering by building upon our existing architectural foundation. By defining tenancy as a behavioral interface and leveraging LinqToDB's <code>ExpressionMethod</code>, we were able to handle both direct and complex projected tenancy relationships with equal elegance. The final generated SQL proves that our composable filter system is robust, correctly layering multi-tenancy rules on top of soft-delete rules for all entities involved in a query.</p><p>This approach significantly reduces the risk of data leakage and removes the burden of security filtering from the business logic developer, fulfilling a core requirement of a true enterprise DAL.</p><p>You can review the complete implementation for this stage of the project on GitHub: <a href=\"https://github.com/ByteAether/EnterpriseDal/tree/part5\" target=\"_blank\" rel=\"noopener noreferrer\">https://github.com/ByteAether/EnterpriseDal/tree/part5</a>.</p><p>Moving forward, we will tackle an even more granular security challenge: a generic, row-based permission system. The goal is to create a mechanism where any entity can be protected, requiring users to have explicit permission for that specific row in the database. If a user lacks the required permission, the entity will be automatically and transparently filtered from all query results, abstracting this complex conditional logic away from the business layer.</p>",
      "date_published": "2025-11-11T00:00:00Z"
    }
    ,
    {
      "id": "https://byteaether.github.io/2025/building-an-enterprise-data-access-layer-automated-soft-delete/",
      "url": "https://byteaether.github.io/2025/building-an-enterprise-data-access-layer-automated-soft-delete/",
      "title": "Building an Enterprise Data Access Layer: Automated Soft-Delete",
      "content_html": "<p>Welcome to the fourth installment of our series on building a robust, enterprise-grade Data Access Layer (DAL) in C#. In our <a href=\"https://byteaether.github.io/2025/building-an-enterprise-data-access-layer-automated-auditing/\">previous post</a>, we established a powerful automated auditing system to track entity creation and modification times (<code>CreatedAt</code>/<code>ModifiedAt</code>). Now, we will tackle another critical enterprise requirement: <strong>soft-delete</strong>.</p><p>In many systems, physically deleting data (<code>DELETE FROM ...</code>) is unacceptable. Regulatory compliance, audit trails, and the simple need for data recovery demand that records are preserved, even when a user &quot;deletes&quot; them. The standard solution is soft-deletion, where a record is marked as inactive instead of being removed.</p><p>This post will detail how we implement a fully automated, transparent, and secure soft-delete mechanism. We will not only intercept delete operations but also ensure that soft-deleted records are automatically filtered out of all read operations, including through entity associations.</p><p>The complete code for this part of the series is available on our GitHub repository: <a href=\"https://github.com/ByteAether/EnterpriseDal/tree/part4\" target=\"_blank\" rel=\"noopener noreferrer\">https://github.com/ByteAether/EnterpriseDal/tree/part4</a>.</p><h2 id=\"the-soft-delete-contract%3A-the-iremovable-interface\" tabindex=\"-1\">The Soft-Delete Contract: The <code>IRemovable</code> Interface</h2><p>Consistent with our architecture, we define entity behaviors through interfaces. For soft-delete, we introduce the <code>IRemovable</code> interface in the <code>DAL.Base/EntityBehavior</code> namespace.</p><pre class=\"language-csharp\"><code class=\"language-csharp\"><span class=\"token keyword\">namespace</span> <span class=\"token namespace\">DAL<span class=\"token punctuation\">.</span>Base<span class=\"token punctuation\">.</span>EntityBehavior</span><span class=\"token punctuation\">;</span>\n\n<span class=\"token punctuation\">[</span><span class=\"token generic-method\"><span class=\"token function\">EntityFilter</span><span class=\"token generic class-name\"><span class=\"token punctuation\">&lt;</span>IRemovable<span class=\"token punctuation\">></span></span></span><span class=\"token punctuation\">(</span><span class=\"token keyword\">nameof</span><span class=\"token punctuation\">(</span>Filter<span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">]</span>\n<span class=\"token keyword\">public</span> <span class=\"token keyword\">interface</span> <span class=\"token class-name\">IRemovable</span> <span class=\"token punctuation\">:</span> <span class=\"token type-list\"><span class=\"token class-name\">IEntity</span></span>\n<span class=\"token punctuation\">{</span>\n\t<span class=\"token return-type class-name\">DateTime<span class=\"token punctuation\">?</span></span> RemovedAt <span class=\"token punctuation\">{</span> <span class=\"token keyword\">get</span><span class=\"token punctuation\">;</span> <span class=\"token keyword\">set</span><span class=\"token punctuation\">;</span> <span class=\"token punctuation\">}</span>\n\n\t<span class=\"token keyword\">private</span> <span class=\"token keyword\">static</span> <span class=\"token return-type class-name\">IQueryable<span class=\"token punctuation\">&lt;</span>T<span class=\"token punctuation\">></span></span> <span class=\"token generic-method\"><span class=\"token function\">Filter</span><span class=\"token generic class-name\"><span class=\"token punctuation\">&lt;</span>T<span class=\"token punctuation\">></span></span></span><span class=\"token punctuation\">(</span><span class=\"token class-name\">IQueryable<span class=\"token punctuation\">&lt;</span>T<span class=\"token punctuation\">></span></span> q<span class=\"token punctuation\">,</span> <span class=\"token class-name\">IDbCtx</span> _<span class=\"token punctuation\">)</span>\n\t\t<span class=\"token keyword\">where</span> <span class=\"token class-name\">T</span> <span class=\"token punctuation\">:</span> <span class=\"token type-list\"><span class=\"token class-name\">IRemovable</span></span>\n\t\t<span class=\"token operator\">=></span> q<span class=\"token punctuation\">.</span><span class=\"token function\">Where</span><span class=\"token punctuation\">(</span>x <span class=\"token operator\">=></span> x<span class=\"token punctuation\">.</span>RemovedAt <span class=\"token operator\">==</span> <span class=\"token keyword\">null</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span></code></pre><p>It introduces a nullable <code>RemovedAt</code> property. When this property is <code>NULL</code>, the record is active. When it contains a timestamp, the record is considered deleted. Any entity that should support soft-deletion will implement this interface.</p><p>Our database schema has been updated accordingly, with a nullable <code>removed_at DATETIME</code> column added to all tables.</p><h2 id=\"the-core-challenge%3A-global-and-composable-query-filters\" tabindex=\"-1\">The Core Challenge: Global and Composable Query Filters</h2><p>Implementing soft-delete presents two significant challenges that a naive approach cannot solve:</p><ol><li><strong>Ubiquitous Filtering:</strong> Soft-deleted records must be hidden from <em>every</em> query by default. Manually adding a <code>Where(x =&gt; x.RemovedAt == null)</code> clause to every data retrieval call is brittle, error-prone, and violates the core principle of our DAL, which is to handle cross-cutting concerns automatically.</li><li><strong>Filtering Through Associations:</strong> This is a more subtle but critical problem. Imagine fetching a <code>Post</code> entity and then accessing its associated <code>Comments</code> collection. The DAL must ensure that any soft-deleted comments are automatically filtered from that collection. A simple <code>WHERE</code> clause on the initial <code>Post</code> query will not propagate to lazily or eagerly loaded associations.</li></ol><p>The solution is to use <strong>Global Query Filters</strong>, a feature supported by ORMs like LinqToDB and Entity Framework Core. A global filter is a <code>WHERE</code> condition that the ORM automatically appends to every query for a specific entity type.</p><p>However, these ORMs typically allow only a <em>single</em> filter expression per entity. Our architectural vision requires multiple, independent behaviors (soft-delete, multi-tenancy , row-level security), each potentially contributing its own filter. An entity could implement <code>IRemovable</code> and <code>ITenanted</code>, requiring two separate filters to be applied simultaneously.</p><h2 id=\"a-composable-architecture-for-global-filters\" tabindex=\"-1\">A Composable Architecture for Global Filters</h2><p>To overcome this limitation, we have engineered a system to aggregate multiple filter expressions into a single, cohesive filter for each entity. This logic is encapsulated within the new <code>DAL.Base/EntityFilter</code> folder.</p><p>The system works in three main steps:</p><ol><li><p><strong>Declaration:</strong> We use a custom attribute, <code>[EntityFilter&lt;T&gt;(nameof(MethodName))]</code>, to associate a filtering method with an interface or class. As seen in the <code>IRemovable</code> interface code above, we declare that its filter logic is located in a static method named <code>Filter</code>. This makes each behavioral interface self-contained.</p></li><li><p><strong>Discovery &amp; Aggregation:</strong> We created a helper that, upon database context initialization, scans all our DAL entities (classes implementing <code>IEntity</code>). For each entity, it traverses its entire inheritance tree including all implemented interfaces. It collects all lambda expressions from the filter methods declared via the <code>EntityFilter</code> attribute. These individual expressions are then combined into a single, larger lambda expression using <code>Expression.AndAlso</code>.</p></li><li><p><strong>Application:</strong> The final, aggregated expression is then applied as a single global filter to the entity using LinqToDB's mapping schema. We trigger this entire process with a single call in our database context <code>DbCtx.InitDataContext()</code>:</p><pre class=\"language-csharp\"><code class=\"language-csharp\"><span class=\"token range operator\">..</span><span class=\"token punctuation\">.</span>\nMappingSchema<span class=\"token punctuation\">.</span><span class=\"token generic-method\"><span class=\"token function\">ApplyEntityFilters</span><span class=\"token generic class-name\"><span class=\"token punctuation\">&lt;</span>DbCtx<span class=\"token punctuation\">></span></span></span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token range operator\">..</span><span class=\"token punctuation\">.</span></code></pre></li></ol><p>This architecture is incredibly flexible. It allows us to define filtering logic alongside the relevant behavior (like <code>IRemovable</code> or, in the future, <code>ITenanted</code>), and the DAL automatically composes these rules at runtime.</p><h2 id=\"integrating-soft-delete-into-the-dal\" tabindex=\"-1\">Integrating Soft-Delete into the DAL</h2><p>With the filter aggregation system in place, we can now integrate the soft-delete logic.</p><h4 id=\"1.-automated-scaffolding-(interceptor.cs)\" tabindex=\"-1\">1. Automated Scaffolding (<code>Interceptor.cs</code>)</h4><p>Our custom scaffolding interceptor is extended to automatically add the <code>IRemovable</code> interface to any generated entity partial class that has a corresponding <code>RemovedAt</code> column in the database. This ensures our C# entity model stays perfectly in sync with our database-first philosophy.</p><h4 id=\"2.-overriding-delete-operations-(crudextensions.cs)\" tabindex=\"-1\">2. Overriding Delete Operations (<code>CrudExtensions.cs</code>)</h4><p>Next, we introduce new <code>RemoveAsync</code> extension methods that replace direct calls to LinqToDB's <code>DeleteAsync</code>. These methods contain the core soft-delete logic.</p><pre class=\"language-csharp\"><code class=\"language-csharp\"><span class=\"token keyword\">public</span> <span class=\"token keyword\">static</span> <span class=\"token return-type class-name\">Task<span class=\"token punctuation\">&lt;</span><span class=\"token keyword\">int</span><span class=\"token punctuation\">></span></span> <span class=\"token generic-method\"><span class=\"token function\">RemoveAsync</span><span class=\"token generic class-name\"><span class=\"token punctuation\">&lt;</span>T<span class=\"token punctuation\">></span></span></span><span class=\"token punctuation\">(</span>\n\t<span class=\"token keyword\">this</span> <span class=\"token class-name\">IQueryable<span class=\"token punctuation\">&lt;</span>T<span class=\"token punctuation\">></span></span> source<span class=\"token punctuation\">,</span>\n\t <span class=\"token class-name\">CancellationToken</span> cancellationToken <span class=\"token operator\">=</span> <span class=\"token keyword\">default</span>\n<span class=\"token punctuation\">)</span> <span class=\"token keyword\">where</span> <span class=\"token class-name\">T</span> <span class=\"token punctuation\">:</span> <span class=\"token type-list\"><span class=\"token keyword\">class</span></span>\n<span class=\"token punctuation\">{</span>\n\t<span class=\"token comment\">// Check if the entity type T implements IRemovable</span>\n\t<span class=\"token keyword\">if</span> <span class=\"token punctuation\">(</span><span class=\"token keyword\">typeof</span><span class=\"token punctuation\">(</span><span class=\"token type-expression class-name\">IRemovable</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">.</span><span class=\"token function\">IsAssignableFrom</span><span class=\"token punctuation\">(</span><span class=\"token keyword\">typeof</span><span class=\"token punctuation\">(</span><span class=\"token type-expression class-name\">T</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span>\n\t<span class=\"token punctuation\">{</span>\n\t\t<span class=\"token comment\">// If so, build an UPDATE query instead of a DELETE</span>\n\t\t<span class=\"token keyword\">return</span> source\n\t\t\t<span class=\"token punctuation\">.</span><span class=\"token function\">Set</span><span class=\"token punctuation\">(</span>x <span class=\"token operator\">=></span> <span class=\"token punctuation\">(</span><span class=\"token punctuation\">(</span>IRemovable<span class=\"token punctuation\">)</span>x<span class=\"token punctuation\">)</span><span class=\"token punctuation\">.</span>RemovedAt<span class=\"token punctuation\">,</span> DateTime<span class=\"token punctuation\">.</span>UtcNow<span class=\"token punctuation\">)</span>\n\t\t\t<span class=\"token punctuation\">.</span><span class=\"token function\">ModifyAsync</span><span class=\"token punctuation\">(</span>cancellationToken<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span> <span class=\"token comment\">// Reuse our existing ModifyAsync</span>\n\t<span class=\"token punctuation\">}</span>\n\n\t<span class=\"token comment\">// Otherwise, perform a hard delete</span>\n\t<span class=\"token keyword\">return</span> source<span class=\"token punctuation\">.</span><span class=\"token function\">DeleteAsync</span><span class=\"token punctuation\">(</span>cancellationToken<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span></code></pre><p>This implementation is transparent to the developer. When they call <code>RemoveAsync</code> on a query for an <code>IRemovable</code> entity, the DAL automatically translates it into an <code>UPDATE</code> statement that sets the <code>RemovedAt</code> timestamp. By cleverly chaining this into our existing <code>ModifyAsync</code> method, we also get <code>ModifiedAt</code> auditing for free.</p><h2 id=\"verification%3A-seeing-it-in-action\" tabindex=\"-1\">Verification: Seeing It in Action</h2><p>Let's validate our implementation. We can write a simple test in <code>Program.cs</code> to remove a user and inspect the generated SQL.</p><ul><li><p><strong>Code in <code>Program.cs</code>:</strong></p><pre class=\"language-csharp\"><code class=\"language-csharp\"><span class=\"token comment\">// Assuming 'ctx' is our DbCtx instance and 'u' is a User entity</span>\n<span class=\"token keyword\">await</span> ctx<span class=\"token punctuation\">.</span><span class=\"token generic-method\"><span class=\"token function\">GetTable</span><span class=\"token generic class-name\"><span class=\"token punctuation\">&lt;</span>User<span class=\"token punctuation\">></span></span></span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span>\n\t<span class=\"token punctuation\">.</span><span class=\"token function\">Where</span><span class=\"token punctuation\">(</span>x <span class=\"token operator\">=></span> x<span class=\"token punctuation\">.</span>Id <span class=\"token operator\">==</span> u<span class=\"token punctuation\">.</span>Id<span class=\"token punctuation\">)</span>\n\t<span class=\"token punctuation\">.</span><span class=\"token function\">RemoveAsync</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\nConsole<span class=\"token punctuation\">.</span><span class=\"token function\">WriteLine</span><span class=\"token punctuation\">(</span>ctx<span class=\"token punctuation\">.</span>LastQuery<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span></code></pre></li><li><p><strong>Generated SQL Output:</strong></p><pre class=\"language-sql\"><code class=\"language-sql\"><span class=\"token keyword\">UPDATE</span>\n\t<span class=\"token punctuation\">[</span><span class=\"token keyword\">user</span><span class=\"token punctuation\">]</span>\n<span class=\"token keyword\">SET</span>\n\t<span class=\"token punctuation\">[</span>removed_at<span class=\"token punctuation\">]</span> <span class=\"token operator\">=</span> <span class=\"token string\">'2025-10-10 10:30:31.029'</span><span class=\"token punctuation\">,</span>\n\t<span class=\"token punctuation\">[</span>modified_at<span class=\"token punctuation\">]</span> <span class=\"token operator\">=</span> <span class=\"token string\">'2025-10-10 10:30:31.029'</span>\n<span class=\"token keyword\">WHERE</span>\n\t<span class=\"token punctuation\">[</span><span class=\"token keyword\">user</span><span class=\"token punctuation\">]</span><span class=\"token punctuation\">.</span><span class=\"token punctuation\">[</span>removed_at<span class=\"token punctuation\">]</span> <span class=\"token operator\">IS</span> <span class=\"token boolean\">NULL</span>\n\t<span class=\"token operator\">AND</span> <span class=\"token punctuation\">[</span><span class=\"token keyword\">user</span><span class=\"token punctuation\">]</span><span class=\"token punctuation\">.</span><span class=\"token punctuation\">[</span>id<span class=\"token punctuation\">]</span> <span class=\"token operator\">=</span> X<span class=\"token string\">'0199CDAC13F5AB8EF21DB4A49A36D2F5'</span></code></pre></li></ul><p>The output confirms our success. The <code>DELETE</code> operation was transparently converted into an <code>UPDATE</code> that sets both <code>removed_at</code> and <code>modified_at</code>. Crucially, notice the <code>WHERE [user].[removed_at] IS NULL</code> clause. Our global filter is automatically applied even to the <code>UPDATE</code> operation itself, ensuring we don't try to re-delete an already deleted record. Any subsequent <code>SELECT</code> query for this user will now automatically fail to find it, as it will not satisfy the global filter.</p><h2 id=\"conclusion-and-next-steps\" tabindex=\"-1\">Conclusion and Next Steps</h2><p>In this post, we have successfully implemented a comprehensive and automated soft-delete system. By building a composable global filter architecture, we have not only solved the immediate challenge but also laid a robust foundation for future DAL capabilities. Our DAL now automatically handles soft-deletes and ensures data integrity across all queries and associations, preventing common security and consistency issues.</p><p>The powerful filter system we built is now ready for its next major task: <strong>multi-tenancy</strong>. In the next article, we will leverage this exact architecture to automatically isolate data between different tenants, preventing one of the most critical types of data leakage in SaaS applications.</p><p>For a deeper dive into the implementation, please explore the source code on our GitHub repository: <a href=\"https://github.com/ByteAether/EnterpriseDal/tree/part4\" target=\"_blank\" rel=\"noopener noreferrer\">https://github.com/ByteAether/EnterpriseDal/tree/part4</a>.</p>",
      "date_published": "2025-11-04T00:00:00Z"
    }
    ,
    {
      "id": "https://byteaether.github.io/2025/building-an-enterprise-data-access-layer-automated-auditing/",
      "url": "https://byteaether.github.io/2025/building-an-enterprise-data-access-layer-automated-auditing/",
      "title": "Building an Enterprise Data Access Layer: Automated Auditing",
      "content_html": "<p>In our <a href=\"https://byteaether.github.io/2025/building-an-enterprise-data-access-layer-database-and-code-structure/\">previous posts</a>, we laid the foundation for our enterprise Data Access Layer (DAL). We established our core principles: a database-first philosophy, the use of C# and <a href=\"https://github.com/linq2db/linq2db\" target=\"_blank\" rel=\"noopener noreferrer\">LinqToDB</a>, and a commitment to automating cross-cutting concerns to enhance security and data integrity. We also structured our project, set up a scaffolding interceptor to enrich our auto-generated entities, and chose <a href=\"https://github.com/ByteAether/Ulid\" target=\"_blank\" rel=\"noopener noreferrer\">ULIDs</a> as our primary keys for their performance benefits.</p><p>This post will build directly on that foundation. We will <a href=\"https://github.com/ByteAether/EnterpriseDal/tree/part3\" target=\"_blank\" rel=\"noopener noreferrer\">implement</a> one of the most common and critical DAL features: automated audit fields. Specifically, we will ensure that <code>CreatedAt</code> and <code>ModifiedAt</code> timestamps are automatically and correctly populated for every entity, without requiring any manual intervention from the developer using the DAL. This automation is key to preventing data inconsistencies and reducing cognitive load on engineers.</p><h2 id=\"a-philosophical-shift%3A-technical-vs.-business-logical-crud\" tabindex=\"-1\">A Philosophical Shift: Technical vs. Business-Logical CRUD</h2><p>Before diving into the implementation, it is important to introduce a conceptual distinction that will guide our design. In software development, we often use the acronym <a href=\"https://en.wikipedia.org/wiki/Create,_read,_update_and_delete\" target=\"_blank\" rel=\"noopener noreferrer\"><strong>CRUD</strong> (Create, Read, Update, Delete)</a> to describe data operations. However, these terms often conflate low-level database actions with high-level business logic.</p><p>To build a truly robust DAL, we must separate these concepts:</p><ul><li><strong>Technical CRUD:</strong> These are the raw database commands: <code>INSERT</code>, <code>UPDATE</code>, <code>DELETE</code>. They represent the fundamental operations the database engine can perform. In our DAL, these will be the underlying methods provided by LinqToDB.</li><li><strong>Business-Logical CRUD:</strong> These are higher-level abstractions that represent business intent: <code>CREATE</code>, <code>MODIFY</code>, <code>REMOVE</code>. A single business-logical operation might involve multiple technical operations, validation, and the application of cross-cutting concerns like auditing or security.</li></ul><p>Our goal is to expose the business-logical methods (<code>CREATE</code>, <code>MODIFY</code>, <code>REMOVE</code>) to the application's business logic layer, while discouraging the direct use of the technical methods (<code>INSERT</code>, <code>UPDATE</code>, <code>DELETE</code>). By doing so, we guarantee that all our automated rules (such as setting audit fields, enforcing soft-delete, or applying multi-tenancy filters) are always executed.</p><p>For now, this separation is a matter of developer discipline. In a future post, we may explore building a <strong>custom Roslyn (static code) analyzer</strong> that warns an engineer if they attempt to call a technical CRUD method directly from a higher-level service, thus enforcing this architectural pattern at compile time.</p><h2 id=\"step-1%3A-defining-behavior-with-icreatable-and-imodifiable\" tabindex=\"-1\">Step 1: Defining Behavior with <code>ICreatable</code> and <code>IModifiable</code></h2><p>To apply auditing logic selectively, we first need a way to identify which entities require it. We achieve this by defining &quot;entity behavior&quot; interfaces. For this feature, we will add two new interfaces to our <code>DAL.Base/EntityBehavior</code> directory:</p><ul><li><strong><code>ICreatable</code></strong>: This interface signifies that an entity has a creation timestamp.<pre class=\"language-csharp\"><code class=\"language-csharp\"><span class=\"token keyword\">public</span> <span class=\"token keyword\">interface</span> <span class=\"token class-name\">ICreatable</span> <span class=\"token punctuation\">:</span> <span class=\"token type-list\"><span class=\"token class-name\">IEntity</span></span>\n<span class=\"token punctuation\">{</span>\n\t\t<span class=\"token return-type class-name\">DateTime</span> CreatedAt <span class=\"token punctuation\">{</span> <span class=\"token keyword\">get</span><span class=\"token punctuation\">;</span> <span class=\"token keyword\">set</span><span class=\"token punctuation\">;</span> <span class=\"token punctuation\">}</span>\n<span class=\"token punctuation\">}</span></code></pre></li><li><strong><code>IModifiable</code></strong>: This interface signifies that an entity has a modification timestamp.<pre class=\"language-csharp\"><code class=\"language-csharp\"><span class=\"token keyword\">public</span> <span class=\"token keyword\">interface</span> <span class=\"token class-name\">IModifiable</span> <span class=\"token punctuation\">:</span> <span class=\"token type-list\"><span class=\"token class-name\">IEntity</span></span>\n<span class=\"token punctuation\">{</span>\n\t\t<span class=\"token return-type class-name\">DateTime</span> ModifiedAt <span class=\"token punctuation\">{</span> <span class=\"token keyword\">get</span><span class=\"token punctuation\">;</span> <span class=\"token keyword\">set</span><span class=\"token punctuation\">;</span> <span class=\"token punctuation\">}</span>\n<span class=\"token punctuation\">}</span></code></pre></li></ul><p>These interfaces are simple contracts. An entity implementing <code>ICreatable</code> guarantees it has a <code>CreatedAt</code> property. This allows us to write generic extension methods that can operate on any entity fulfilling this contract.</p><h2 id=\"step-2%3A-evolving-the-database-and-scaffolding\" tabindex=\"-1\">Step 2: Evolving the Database and Scaffolding</h2><p>Following our database-first approach, the next step is to update our database schema. We add <code>created_at DATETIME not null</code> and <code>modified_at DATETIME not null</code> columns to all four of our tables: <code>tenant</code>, <code>user</code>, <code>post</code>, and <code>comment</code>.</p><p>With the database schema updated, we can enhance our scaffolding interceptor. In addition to adding the <code>IEntity</code> and <code>IIdentifiable&lt;Ulid&gt;</code> interfaces as it did before, the interceptor will now inspect the properties of each table during code generation.</p><ul><li>If a table contains a property named <code>CreatedAt</code>, the interceptor will automatically add <code>ICreatable</code> interface to the generated partial class definition.</li><li>If a table contains a property named <code>ModifiedAt</code>, it will add <code>IModifiable</code> interface.</li></ul><p>After running the scaffolding tool, our generated <code>User</code> entity, for example, will now have a signature like this in <code>DbCtx.generated.cs</code>:</p><pre class=\"language-csharp\"><code class=\"language-csharp\"><span class=\"token keyword\">public</span> <span class=\"token keyword\">partial</span> <span class=\"token keyword\">class</span> <span class=\"token class-name\">User</span> <span class=\"token punctuation\">:</span> <span class=\"token type-list\"><span class=\"token class-name\">IEntity</span><span class=\"token punctuation\">,</span> <span class=\"token class-name\">IIdentifiable<span class=\"token punctuation\">&lt;</span>Ulid<span class=\"token punctuation\">></span></span><span class=\"token punctuation\">,</span> <span class=\"token class-name\">ICreatable</span><span class=\"token punctuation\">,</span> <span class=\"token class-name\">IModifiable</span></span>\n<span class=\"token punctuation\">{</span>\n\t\t<span class=\"token comment\">// ... generated properties</span>\n<span class=\"token punctuation\">}</span></code></pre><p>This powerful combination of a database-first approach and an intelligent interceptor ensures our C# entity definitions always stay synchronized with the database schema and its intended behaviors, without manual intervention.</p><h2 id=\"step-3%3A-implementing-the-business-logic-in-crudextensions.cs\" tabindex=\"-1\">Step 3: Implementing the Business Logic in <code>CrudExtensions.cs</code></h2><p>Now that our entities automatically implement the correct interfaces, we can create the extension methods that constitute our business-logical <code>CREATE</code> and <code>MODIFY</code> operations. We'll place these in a new file, <a href=\"https://github.com/ByteAether/EnterpriseDal/blob/part3/DAL.Base/CrudExtensions.cs\" target=\"_blank\" rel=\"noopener noreferrer\"><code>DAL.Base/CrudExtensions.cs</code></a>.</p><h3 id=\"the-createasync-method\" tabindex=\"-1\">The <code>CreateAsync</code> Method</h3><p>The <code>CreateAsync</code> method is an extension on <code>DbCtx</code>. It takes either an entity object or an enumerable of them, automatically sets the audit fields, and then calls the underlying technical <code>INSERT</code> operation.</p><pre class=\"language-csharp\"><code class=\"language-csharp\"><span class=\"token keyword\">public</span> <span class=\"token keyword\">static</span> <span class=\"token keyword\">async</span> <span class=\"token return-type class-name\">Task<span class=\"token punctuation\">&lt;</span><span class=\"token keyword\">long</span><span class=\"token punctuation\">></span></span> <span class=\"token generic-method\"><span class=\"token function\">CreateAsync</span><span class=\"token generic class-name\"><span class=\"token punctuation\">&lt;</span>T<span class=\"token punctuation\">></span></span></span><span class=\"token punctuation\">(</span>\n\t<span class=\"token keyword\">this</span> <span class=\"token class-name\">DbCtx</span> ctx<span class=\"token punctuation\">,</span>\n\t<span class=\"token class-name\">IEnumerable<span class=\"token punctuation\">&lt;</span>T<span class=\"token punctuation\">></span></span> entities<span class=\"token punctuation\">,</span>\n\t<span class=\"token class-name\">CancellationToken</span> cancellationToken <span class=\"token operator\">=</span> <span class=\"token keyword\">default</span>\n<span class=\"token punctuation\">)</span> <span class=\"token keyword\">where</span> <span class=\"token class-name\">T</span> <span class=\"token punctuation\">:</span> <span class=\"token type-list\"><span class=\"token keyword\">class</span><span class=\"token punctuation\">,</span> <span class=\"token class-name\">IEntity</span></span>\n\t<span class=\"token operator\">=></span> <span class=\"token punctuation\">(</span>\n\t\t<span class=\"token keyword\">await</span> ctx<span class=\"token punctuation\">.</span><span class=\"token function\">BulkCopyAsync</span><span class=\"token punctuation\">(</span>\n\t\t\tentities<span class=\"token punctuation\">.</span><span class=\"token function\">Select</span><span class=\"token punctuation\">(</span>e <span class=\"token operator\">=></span>\n\t\t\t\t<span class=\"token punctuation\">{</span>\n\t\t\t\t\t<span class=\"token keyword\">if</span> <span class=\"token punctuation\">(</span>e <span class=\"token keyword\">is</span> <span class=\"token class-name\">ICreatable</span> creatable <span class=\"token operator\">&amp;&amp;</span> creatable<span class=\"token punctuation\">.</span>CreatedAt <span class=\"token operator\">==</span> <span class=\"token keyword\">default</span><span class=\"token punctuation\">)</span>\n\t\t\t\t\t<span class=\"token punctuation\">{</span>\n\t\t\t\t\t\tcreatable<span class=\"token punctuation\">.</span>CreatedAt <span class=\"token operator\">=</span> DateTime<span class=\"token punctuation\">.</span>UtcNow<span class=\"token punctuation\">;</span>\n\t\t\t\t\t<span class=\"token punctuation\">}</span>\n\n\t\t\t\t\t<span class=\"token keyword\">if</span> <span class=\"token punctuation\">(</span>e <span class=\"token keyword\">is</span> <span class=\"token class-name\">IModifiable</span> updateable <span class=\"token operator\">&amp;&amp;</span> updateable<span class=\"token punctuation\">.</span>ModifiedAt <span class=\"token operator\">==</span> <span class=\"token keyword\">default</span><span class=\"token punctuation\">)</span>\n\t\t\t\t\t<span class=\"token punctuation\">{</span>\n\t\t\t\t\t\tupdateable<span class=\"token punctuation\">.</span>ModifiedAt <span class=\"token operator\">=</span> DateTime<span class=\"token punctuation\">.</span>UtcNow<span class=\"token punctuation\">;</span>\n\t\t\t\t\t<span class=\"token punctuation\">}</span>\n\n\t\t\t\t\t<span class=\"token keyword\">return</span> e<span class=\"token punctuation\">;</span>\n\t\t\t\t<span class=\"token punctuation\">}</span>\n\t\t\t<span class=\"token punctuation\">)</span><span class=\"token punctuation\">,</span>\n\t\t\tcancellationToken\n\t\t<span class=\"token punctuation\">)</span>\n\t<span class=\"token punctuation\">)</span><span class=\"token punctuation\">.</span>RowsCopied<span class=\"token punctuation\">;</span>\n\n<span class=\"token keyword\">public</span> <span class=\"token keyword\">static</span> <span class=\"token return-type class-name\">Task<span class=\"token punctuation\">&lt;</span><span class=\"token keyword\">long</span><span class=\"token punctuation\">></span></span> <span class=\"token generic-method\"><span class=\"token function\">CreateAsync</span><span class=\"token generic class-name\"><span class=\"token punctuation\">&lt;</span>T<span class=\"token punctuation\">></span></span></span><span class=\"token punctuation\">(</span>\n\t<span class=\"token keyword\">this</span> <span class=\"token class-name\">DbCtx</span> ctx<span class=\"token punctuation\">,</span>\n\t<span class=\"token class-name\">T</span> entity<span class=\"token punctuation\">,</span>\n\t<span class=\"token class-name\">CancellationToken</span> cancellationToken <span class=\"token operator\">=</span> <span class=\"token keyword\">default</span>\n<span class=\"token punctuation\">)</span> <span class=\"token keyword\">where</span> <span class=\"token class-name\">T</span> <span class=\"token punctuation\">:</span> <span class=\"token type-list\"><span class=\"token keyword\">class</span><span class=\"token punctuation\">,</span> <span class=\"token class-name\">IEntity</span></span>\n\t<span class=\"token operator\">=></span> ctx<span class=\"token punctuation\">.</span><span class=\"token function\">CreateAsync</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">[</span><span class=\"token attribute\"><span class=\"token class-name\">entity</span></span><span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span> cancellationToken<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span></code></pre><p>By checking the interfaces, these methods correctly handle any entity. If an entity only implements <code>ICreatable</code>, only <code>CreatedAt</code> is set. If it implements both, both timestamps are set.</p><h3 id=\"the-modifyasync-method\" tabindex=\"-1\">The <code>ModifyAsync</code> Method</h3><p>The <code>ModifyAsync</code> methods are slightly different. They are extensions on either <code>DbCtx</code> or <code>IQueryable&lt;T&gt;</code>, designed to work with LinqToDB's fluent update syntax.</p><pre class=\"language-csharp\"><code class=\"language-csharp\"><span class=\"token keyword\">public</span> <span class=\"token keyword\">static</span> <span class=\"token return-type class-name\">Task<span class=\"token punctuation\">&lt;</span><span class=\"token keyword\">int</span><span class=\"token punctuation\">></span></span> <span class=\"token generic-method\"><span class=\"token function\">ModifyAsync</span><span class=\"token generic class-name\"><span class=\"token punctuation\">&lt;</span>T<span class=\"token punctuation\">></span></span></span><span class=\"token punctuation\">(</span>\n\t<span class=\"token keyword\">this</span> <span class=\"token class-name\">DbCtx</span> ctx<span class=\"token punctuation\">,</span>\n\t<span class=\"token class-name\">T</span> entity<span class=\"token punctuation\">,</span>\n\t<span class=\"token class-name\">CancellationToken</span> cancellationToken <span class=\"token operator\">=</span> <span class=\"token keyword\">default</span>\n<span class=\"token punctuation\">)</span> <span class=\"token keyword\">where</span> <span class=\"token class-name\">T</span> <span class=\"token punctuation\">:</span> <span class=\"token type-list\"><span class=\"token keyword\">class</span><span class=\"token punctuation\">,</span> <span class=\"token class-name\">IEntity</span></span>\n<span class=\"token punctuation\">{</span>\n\t<span class=\"token keyword\">if</span> <span class=\"token punctuation\">(</span>entity <span class=\"token keyword\">is</span> <span class=\"token class-name\">IModifiable</span> updateable<span class=\"token punctuation\">)</span>\n\t<span class=\"token punctuation\">{</span>\n\t\tupdateable<span class=\"token punctuation\">.</span>ModifiedAt <span class=\"token operator\">=</span> DateTime<span class=\"token punctuation\">.</span>UtcNow<span class=\"token punctuation\">;</span>\n\t<span class=\"token punctuation\">}</span>\n\n\t<span class=\"token keyword\">return</span> ctx<span class=\"token punctuation\">.</span><span class=\"token generic-method\"><span class=\"token function\">GetTable</span><span class=\"token generic class-name\"><span class=\"token punctuation\">&lt;</span>T<span class=\"token punctuation\">></span></span></span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">.</span><span class=\"token function\">UpdateOptimisticAsync</span><span class=\"token punctuation\">(</span>entity<span class=\"token punctuation\">,</span> cancellationToken<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span>\n\n<span class=\"token keyword\">public</span> <span class=\"token keyword\">static</span> <span class=\"token return-type class-name\">Task<span class=\"token punctuation\">&lt;</span><span class=\"token keyword\">int</span><span class=\"token punctuation\">></span></span> <span class=\"token generic-method\"><span class=\"token function\">ModifyAsync</span><span class=\"token generic class-name\"><span class=\"token punctuation\">&lt;</span>T<span class=\"token punctuation\">></span></span></span><span class=\"token punctuation\">(</span>\n\t<span class=\"token keyword\">this</span> <span class=\"token class-name\">IUpdatable<span class=\"token punctuation\">&lt;</span>T<span class=\"token punctuation\">></span></span> source<span class=\"token punctuation\">,</span>\n\t<span class=\"token class-name\">CancellationToken</span> cancellationToken <span class=\"token operator\">=</span> <span class=\"token keyword\">default</span>\n<span class=\"token punctuation\">)</span> <span class=\"token keyword\">where</span> <span class=\"token class-name\">T</span> <span class=\"token punctuation\">:</span> <span class=\"token type-list\"><span class=\"token keyword\">class</span></span>\n<span class=\"token punctuation\">{</span>\n\t<span class=\"token keyword\">if</span> <span class=\"token punctuation\">(</span><span class=\"token keyword\">typeof</span><span class=\"token punctuation\">(</span><span class=\"token type-expression class-name\">IModifiable</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">.</span><span class=\"token function\">IsAssignableFrom</span><span class=\"token punctuation\">(</span><span class=\"token keyword\">typeof</span><span class=\"token punctuation\">(</span><span class=\"token type-expression class-name\">T</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span>\n\t<span class=\"token punctuation\">{</span>\n\t\tsource <span class=\"token operator\">=</span> source<span class=\"token punctuation\">.</span><span class=\"token function\">Set</span><span class=\"token punctuation\">(</span>\n\t\t\tx <span class=\"token operator\">=></span> Sql<span class=\"token punctuation\">.</span><span class=\"token generic-method\"><span class=\"token function\">Property</span><span class=\"token generic class-name\"><span class=\"token punctuation\">&lt;</span>DateTime<span class=\"token punctuation\">></span></span></span><span class=\"token punctuation\">(</span>x<span class=\"token punctuation\">,</span> <span class=\"token keyword\">nameof</span><span class=\"token punctuation\">(</span>IModifiable<span class=\"token punctuation\">.</span>ModifiedAt<span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">,</span>\n\t\t\tDateTime<span class=\"token punctuation\">.</span>UtcNow\n\t\t<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\t<span class=\"token punctuation\">}</span>\n\n\t<span class=\"token keyword\">return</span> source<span class=\"token punctuation\">.</span><span class=\"token function\">UpdateAsync</span><span class=\"token punctuation\">(</span>cancellationToken<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span>\n\n<span class=\"token keyword\">public</span> <span class=\"token keyword\">static</span> <span class=\"token return-type class-name\">Task<span class=\"token punctuation\">&lt;</span><span class=\"token keyword\">int</span><span class=\"token punctuation\">></span></span> <span class=\"token generic-method\"><span class=\"token function\">ModifyAsync</span><span class=\"token generic class-name\"><span class=\"token punctuation\">&lt;</span>T<span class=\"token punctuation\">></span></span></span><span class=\"token punctuation\">(</span>\n\t<span class=\"token keyword\">this</span> <span class=\"token class-name\">IQueryable<span class=\"token punctuation\">&lt;</span>T<span class=\"token punctuation\">></span></span> source<span class=\"token punctuation\">,</span>\n\t<span class=\"token class-name\">CancellationToken</span> cancellationToken <span class=\"token operator\">=</span> <span class=\"token keyword\">default</span>\n<span class=\"token punctuation\">)</span> <span class=\"token keyword\">where</span> <span class=\"token class-name\">T</span> <span class=\"token punctuation\">:</span> <span class=\"token type-list\"><span class=\"token keyword\">class</span></span>\n\t<span class=\"token operator\">=></span> source<span class=\"token punctuation\">.</span><span class=\"token function\">AsUpdatable</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">.</span><span class=\"token function\">ModifyAsync</span><span class=\"token punctuation\">(</span>cancellationToken<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span></code></pre><p>These methods cleverly inspect the generic type <code>T</code>. If <code>T</code> implements <code>IModifiable</code>, it injects a <code>.Set()</code> call to update the <code>ModifiedAt</code> field with the current timestamp before executing the final <code>UpdateAsync()</code> call or assigns the current timestamp to an entity's property. This ensures that any update operation expressed through this fluent API is properly audited.</p><h3 id=\"a-note-on-iqueryexpressioninterceptor\" tabindex=\"-1\">A Note on <code>IQueryExpressionInterceptor</code></h3><p>You might be wondering if there is a more integrated way to achieve this within LinqToDB, rather than creating wrapper extension methods. LinqToDB provides a <a href=\"https://github.com/linq2db/linq2db/blob/59940d23f474599b6820a83f3116cc256dd42cc1/Source/LinqToDB/Interceptors/IQueryExpressionInterceptor.cs\" target=\"_blank\" rel=\"noopener noreferrer\"><code>IQueryExpressionInterceptor</code></a> interface, which is designed to intercept every query expression before its translation into SQL. In theory, this would be the perfect place to inject our auditing logic.</p><p>Unfortunately, as of this writing, the implementation of this interceptor is not consistently triggered across all operation types. For example, it does not fire for <code>INSERT</code> operations or for calls to <code>IDataContext.UpdateAsync&lt;T&gt;()</code>, although it does trigger for the fluent <code>IUpdatable&lt;T&gt;.UpdateAsync()</code> method. This inconsistency makes it unsuitable for building a reliable, all-encompassing auditing system.</p><p>Should this behavior be fixed in a future version of LinqToDB to reliably intercept all query expressions, it would offer a more elegant solution. We could then centralize our auditing logic in the interceptor and use the standard <code>InsertAsync</code> and <code>UpdateAsync</code> methods directly. This would eliminate the need for our custom <code>CreateAsync</code> and <code>ModifyAsync</code> wrappers and the potential need for a static code analyzer to enforce their use. If such an update occurs, we will certainly explore it in a follow-up article.</p><h2 id=\"seeing-it-in-action\" tabindex=\"-1\">Seeing it in Action</h2><p>With all the pieces in place, we can verify that our implementation works as expected. We can write a small test in <code>Program.cs</code> to create and then modify a <code>User</code> entity. Notice that our C# code never explicitly provides a value for <code>CreatedAt</code> or <code>ModifiedAt</code>.</p><pre class=\"language-csharp\"><code class=\"language-csharp\"><span class=\"token keyword\">await</span> ctx<span class=\"token punctuation\">.</span><span class=\"token function\">BeginTransactionAsync</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\n<span class=\"token comment\">// Create entity</span>\n<span class=\"token class-name\"><span class=\"token keyword\">var</span></span> u <span class=\"token operator\">=</span> <span class=\"token keyword\">new</span> <span class=\"token constructor-invocation class-name\">User</span>\n<span class=\"token punctuation\">{</span>\n\t\tId <span class=\"token operator\">=</span> Ulid<span class=\"token punctuation\">.</span><span class=\"token function\">New</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">,</span>\n\t\tUsername <span class=\"token operator\">=</span> <span class=\"token string\">\"asd123\"</span>\n<span class=\"token punctuation\">}</span><span class=\"token punctuation\">;</span>\n<span class=\"token keyword\">await</span> ctx<span class=\"token punctuation\">.</span><span class=\"token function\">CreateAsync</span><span class=\"token punctuation\">(</span>u<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\nConsole<span class=\"token punctuation\">.</span><span class=\"token function\">WriteLine</span><span class=\"token punctuation\">(</span>ctx<span class=\"token punctuation\">.</span>LastQuery<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\n<span class=\"token comment\">// Modify entity</span>\n<span class=\"token keyword\">await</span> ctx<span class=\"token punctuation\">.</span><span class=\"token generic-method\"><span class=\"token function\">GetTable</span><span class=\"token generic class-name\"><span class=\"token punctuation\">&lt;</span>User<span class=\"token punctuation\">></span></span></span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span>\n\t\t<span class=\"token punctuation\">.</span><span class=\"token function\">Where</span><span class=\"token punctuation\">(</span>x <span class=\"token operator\">=></span> x<span class=\"token punctuation\">.</span>Id <span class=\"token operator\">==</span> u<span class=\"token punctuation\">.</span>Id<span class=\"token punctuation\">)</span>\n\t\t<span class=\"token punctuation\">.</span><span class=\"token function\">Set</span><span class=\"token punctuation\">(</span>x <span class=\"token operator\">=></span> x<span class=\"token punctuation\">.</span>Username<span class=\"token punctuation\">,</span> <span class=\"token string\">\"asd1234\"</span><span class=\"token punctuation\">)</span>\n\t\t<span class=\"token punctuation\">.</span><span class=\"token function\">ModifyAsync</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\nConsole<span class=\"token punctuation\">.</span><span class=\"token function\">WriteLine</span><span class=\"token punctuation\">(</span>ctx<span class=\"token punctuation\">.</span>LastQuery<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\n<span class=\"token keyword\">await</span> ctx<span class=\"token punctuation\">.</span><span class=\"token function\">RollbackTransactionAsync</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span></code></pre><p>Executing this code produces the following SQL, captured from LinqToDB's <code>LastQuery</code> property:</p><pre class=\"language-sql\"><code class=\"language-sql\"><span class=\"token keyword\">INSERT</span> <span class=\"token keyword\">INTO</span> <span class=\"token punctuation\">[</span><span class=\"token keyword\">user</span><span class=\"token punctuation\">]</span>\n<span class=\"token punctuation\">(</span>\n\t\t\t\t<span class=\"token punctuation\">[</span>id<span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span>\n\t\t\t\t<span class=\"token punctuation\">[</span>tenant_id<span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span>\n\t\t\t\t<span class=\"token punctuation\">[</span>username<span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span>\n\t\t\t\t<span class=\"token punctuation\">[</span>created_at<span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span>\n\t\t\t\t<span class=\"token punctuation\">[</span>modified_at<span class=\"token punctuation\">]</span>\n<span class=\"token punctuation\">)</span>\n<span class=\"token keyword\">VALUES</span>\n<span class=\"token punctuation\">(</span>X<span class=\"token string\">'0199C31CC424CFC7293147E8A6AE578C'</span><span class=\"token punctuation\">,</span>X<span class=\"token string\">'00000000000000000000000000000000'</span><span class=\"token punctuation\">,</span><span class=\"token string\">'asd123'</span><span class=\"token punctuation\">,</span><span class=\"token string\">'2025-10-08 09:17:46.472'</span><span class=\"token punctuation\">,</span><span class=\"token string\">'2025-10-08 09:17:46.473'</span><span class=\"token punctuation\">)</span>\n\n<span class=\"token keyword\">UPDATE</span>\n\t\t\t\t<span class=\"token punctuation\">[</span><span class=\"token keyword\">user</span><span class=\"token punctuation\">]</span>\n<span class=\"token keyword\">SET</span>\n\t\t\t\t<span class=\"token punctuation\">[</span>username<span class=\"token punctuation\">]</span> <span class=\"token operator\">=</span> <span class=\"token string\">'asd1234'</span><span class=\"token punctuation\">,</span>\n\t\t\t\t<span class=\"token punctuation\">[</span>modified_at<span class=\"token punctuation\">]</span> <span class=\"token operator\">=</span> <span class=\"token string\">'2025-10-08 09:17:46.506'</span>\n<span class=\"token keyword\">WHERE</span>\n\t\t\t\t<span class=\"token punctuation\">[</span><span class=\"token keyword\">user</span><span class=\"token punctuation\">]</span><span class=\"token punctuation\">.</span><span class=\"token punctuation\">[</span>id<span class=\"token punctuation\">]</span> <span class=\"token operator\">=</span> X<span class=\"token string\">'0199C31CC424CFC7293147E8A6AE578C'</span></code></pre><p>The output confirms our success. The <code>INSERT</code> statement correctly includes values for both <code>created_at</code> and <code>modified_at</code>. The <code>UPDATE</code> statement automatically includes the <code>SET</code> clause for <code>modified_at</code>. Our DAL now handles these audit fields transparently, reliably, and without any developer effort.</p><p>The complete implementation for this part of the series can be found on GitHub: <a href=\"https://github.com/ByteAether/EnterpriseDal/tree/part3\" target=\"_blank\" rel=\"noopener noreferrer\">https://github.com/ByteAether/EnterpriseDal/tree/part3</a>.</p><h2 id=\"conclusion-and-next-steps\" tabindex=\"-1\">Conclusion and Next Steps</h2><p>In this post, we successfully implemented automated auditing for creation and modification timestamps. We introduced the critical distinction between technical and business-logical CRUD operations, defined entity behaviors using interfaces, and leveraged our scaffolding interceptor to automate their implementation. Finally, we created generic extension methods that apply auditing rules consistently across all relevant entities.</p><p>In the next episode, we will tackle another core DAL capability: <strong>Soft-Delete</strong>. This feature is similar in that it will rely on a new interface, but it introduces an additional layer of complexity: we must also implement a global query filter to automatically exclude soft-deleted records from all read operations, a crucial step in preventing data leakage and ensuring application correctness.</p>",
      "date_published": "2025-10-24T00:00:00Z"
    }
    ,
    {
      "id": "https://byteaether.github.io/2025/building-an-enterprise-data-access-layer-database-and-code-structure/",
      "url": "https://byteaether.github.io/2025/building-an-enterprise-data-access-layer-database-and-code-structure/",
      "title": "Building an Enterprise Data Access Layer: Database and Code Structure",
      "content_html": "<p>Welcome back to our series on building a robust, enterprise-grade Data Access Layer (DAL) with C# and LinqToDB. In our <a href=\"https://byteaether.github.io/2025/building-an-enterprise-data-access-layer-the-foundation/\">previous post</a>, we established the core principles and non-negotiable capabilities our DAL must possess, including automatic multi-tenancy filtering, soft-delete, and auditing. Now, we'll begin laying the foundation for this powerful system by defining the database schema and the initial code structure.</p><p>This post will explore the deliberate technical choices we have made for this project's foundation. While these decisions may seem straightforward at first glance, each one serves a specific purpose in building a scalable, secure, and maintainable data layer that abstracts complexity away from the business logic.</p><h2 id=\"the-database-first-philosophy\" tabindex=\"-1\">The Database-First Philosophy</h2><p>Before we define the structure, let's address the approach. This project is firmly rooted in a database-first philosophy. In my view, the database is a core engineering artifact and should be treated as such, designed and managed using purpose-built database engineering tools. The schema is the source of truth, and our code should be a faithful reflection of that schema.</p><p>This approach ensures that database constraints, indexes, and data types are optimized for the database engine itself, rather than being an afterthought generated by an Object-Relational Mapper (ORM). It provides a clear separation of concerns, allowing database administrators and backend engineers to collaborate on a well-defined and performant schema.</p><h2 id=\"our-core-data-model\" tabindex=\"-1\">Our Core Data Model</h2><p>For this series, we will build a simple data model to represent a multi-tenant blogging platform. The model consists of four essential tables, each with a clear purpose and relationship to the others.</p><ul><li><code>tenant</code>: This table represents the top-level entity in our multi-tenant application. Every user belongs to a single tenant, and all data within a tenant is isolated from others. It contains a unique <code>id</code> and a <code>name</code>.</li><li><code>user</code>: This table holds our user accounts. Each user is associated with a specific <code>tenant</code> via a foreign key (<code>tenant_id</code>). It also contains a <code>username</code>.</li><li><code>post</code>: A <code>post</code> is a content unit created by a <code>user</code>. It references the <code>user</code> via a foreign key (<code>user_id</code>). A post has a <code>title</code> and the main <code>content</code>.</li><li><code>comment</code>: This table stores comments on a post. Each <code>comment</code> is linked to a <code>post</code> via a foreign key (<code>post_id</code>) and contains its <code>content</code>.</li></ul><p>The relationships are as follows:</p><ul><li><code>comment.post_id</code> references <code>post.id</code></li><li><code>post.user_id</code> references <code>user.id</code></li><li><code>user.tenant_id</code> references <code>tenant.id</code></li></ul><p>This simple structure allows us to demonstrate how the DAL can automatically enforce rules like multi-tenancy and permissions by navigating these relationships.</p><h2 id=\"the-power-of-the-ulid\" tabindex=\"-1\">The Power of the ULID</h2><p>A cornerstone of our database design is the choice of <strong><a href=\"https://github.com/ulid/spec\" target=\"_blank\" rel=\"noopener noreferrer\">ULID (Universal Unique Lexicographically Sortable Identifier)</a></strong> as the primary key for all tables. A ULID is a 128-bit identifier that combines the uniqueness of a UUID with the sortable properties of a timestamp.</p><p>In our C# code, we will use the <a href=\"https://github.com/ByteAether/Ulid\" target=\"_blank\" rel=\"noopener noreferrer\">ByteAether.Ulid</a> library. This library provides a native <code>Ulid</code> type that is specifically designed for this purpose. In the database, ULIDs are stored as 128-bit binary, which is the most efficient representation.</p><p>The key advantage of ULIDs is their sortability. Because the first 48 bits of a ULID are a timestamp, when you sort a column of ULIDs, you are also sorting by the order of creation. This is incredibly beneficial for performance on append-only tables, as new records are always written to the end of the table, minimizing fragmentation. It also makes it easy to find the most recent records without a separate timestamp column.</p><p>For databases like SQLite, this property allows us to declare our tables <code>WITHOUT ROWID</code>. This SQLite-specific feature, when used with a ULID primary key, removes the default internal 64-bit integer row identifier, making the ULID the sole source of record identity and order. Since ULIDs are inherently sortable, the table is naturally clustered by creation time, which optimizes read operations on the most recent data.</p><p>Here is the full SQL schema for our four tables:</p><pre class=\"language-sql\"><code class=\"language-sql\"><span class=\"token keyword\">create</span> <span class=\"token keyword\">table</span> tenant\n<span class=\"token punctuation\">(</span>\n\tid   ulid        <span class=\"token operator\">not</span> <span class=\"token boolean\">null</span>\n\t\t<span class=\"token keyword\">primary</span> <span class=\"token keyword\">key</span><span class=\"token punctuation\">,</span>\n\tname <span class=\"token keyword\">VARCHAR</span><span class=\"token punctuation\">(</span><span class=\"token number\">64</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">not</span> <span class=\"token boolean\">null</span>\n\t\t<span class=\"token keyword\">unique</span>\n<span class=\"token punctuation\">)</span>\n\twithout rowid<span class=\"token punctuation\">;</span>\n\n<span class=\"token keyword\">create</span> <span class=\"token keyword\">table</span> <span class=\"token keyword\">user</span>\n<span class=\"token punctuation\">(</span>\n\tid        ulid        <span class=\"token operator\">not</span> <span class=\"token boolean\">null</span>\n\t\t<span class=\"token keyword\">primary</span> <span class=\"token keyword\">key</span><span class=\"token punctuation\">,</span>\n\ttenant_id ulid        <span class=\"token operator\">not</span> <span class=\"token boolean\">null</span>\n\t\t<span class=\"token keyword\">references</span> tenant<span class=\"token punctuation\">,</span>\n\tusername  <span class=\"token keyword\">VARCHAR</span><span class=\"token punctuation\">(</span><span class=\"token number\">64</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">not</span> <span class=\"token boolean\">null</span>\n<span class=\"token punctuation\">)</span>\n\twithout rowid<span class=\"token punctuation\">;</span>\n\n<span class=\"token keyword\">create</span> <span class=\"token keyword\">table</span> post\n<span class=\"token punctuation\">(</span>\n\tid      ulid        <span class=\"token operator\">not</span> <span class=\"token boolean\">null</span>\n\t\t<span class=\"token keyword\">primary</span> <span class=\"token keyword\">key</span><span class=\"token punctuation\">,</span>\n\tuser_id ulid        <span class=\"token operator\">not</span> <span class=\"token boolean\">null</span>\n\t\t<span class=\"token keyword\">references</span> <span class=\"token keyword\">user</span><span class=\"token punctuation\">,</span>\n\ttitle   <span class=\"token keyword\">varchar</span><span class=\"token punctuation\">(</span><span class=\"token number\">64</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">not</span> <span class=\"token boolean\">null</span><span class=\"token punctuation\">,</span>\n\tcontent <span class=\"token keyword\">text</span>        <span class=\"token operator\">not</span> <span class=\"token boolean\">null</span>\n<span class=\"token punctuation\">)</span>\n\twithout rowid<span class=\"token punctuation\">;</span>\n\n<span class=\"token keyword\">create</span> <span class=\"token keyword\">table</span> <span class=\"token keyword\">comment</span>\n<span class=\"token punctuation\">(</span>\n\tid      ulid <span class=\"token operator\">not</span> <span class=\"token boolean\">null</span>\n\t\t<span class=\"token keyword\">primary</span> <span class=\"token keyword\">key</span><span class=\"token punctuation\">,</span>\n\tpost_id ulid <span class=\"token operator\">not</span> <span class=\"token boolean\">null</span>\n\t\t<span class=\"token keyword\">references</span> post<span class=\"token punctuation\">,</span>\n\tcontent <span class=\"token keyword\">text</span> <span class=\"token operator\">not</span> <span class=\"token boolean\">null</span>\n<span class=\"token punctuation\">)</span>\n\twithout rowid<span class=\"token punctuation\">;</span></code></pre><h2 id=\"the-scaffolding-and-code-structure\" tabindex=\"-1\">The Scaffolding and Code Structure</h2><p>With our database schema defined, we now turn to the C# code. We will use <a href=\"https://linq2db.github.io/articles/CLI.html\" target=\"_blank\" rel=\"noopener noreferrer\">LinqToDB's built-in scaffolding tool</a> to generate our C# entity classes and a database context. However, we will enhance this process with a custom scaffolding interceptor.</p><p><a href=\"https://github.com/ByteAether/EnterpriseDal/tree/part2\" target=\"_blank\" rel=\"noopener noreferrer\">Our core code structure</a> will be organized into a few key areas:</p><h3 id=\"dal.base\" tabindex=\"-1\"><code>DAL.Base</code></h3><p>This namespace holds the fundamental interfaces that all of our entities may inherit from. We're keeping it simple for now, with two interfaces:</p><ul><li><code>IEntity</code>: A simple marker interface that all of our data access layer entities will inherit. While it is empty now, its existence allows us to build generic services and behaviors that can be applied to any entity in the DAL.</li><li><code>IIdentifiable&lt;T&gt;</code>: An interface that marks any entity that has a property named <code>Id</code> of type <code>T</code>. This is a crucial interface for our core services, as it allows us to build generic logic that can operate on any entity with an identifier. For example, a generic <code>queryable.WhereId()</code> method would require this interface.</li></ul><h3 id=\"dal.context\" tabindex=\"-1\"><code>DAL.Context</code></h3><p>This namespace contains our database context and all generated entity classes. We've made a key architectural decision here: all generated classes will reside in a single file named DbCtx.generated.cs. This simplifies the regeneration process, as you only need to update a single file when the database schema changes.</p><ul><li><code>DbCtx.generated.cs</code>: This file will be created by the LinqToDB scaffolding tool. It will contain our <code>DbCtx</code> class, as well as partial classes for our <code>Tenant</code>, <code>User</code>, <code>Post</code>, and <code>Comment</code> entities.</li><li><code>DbCtx.cs</code>: This is the complementary file for our <code>DbCtx.generated.cs</code>. The <code>DbCtx</code> class is a partial class, so we can extend it with our own methods and properties here, leaving the generated file untouched.</li></ul><p>A key piece of the <code>DbCtx.cs</code> file is the <code>partial void InitDataContext()</code> method. This method, which is automatically called by the generated constructor in <code>DbCtx.generated.cs</code>, is where we will define the runtime type mapping for our <code>Ulid</code>s. This ensures that when LinqToDB reads a <code>ulid</code> type from the database (as a byte array), it is correctly converted into a <code>Ulid</code> C# object, and vice versa. This runtime mapping is the counterpart to the design-time mapping handled by our <code>ScaffoldInterceptor</code>.</p><blockquote><h4 id=\"note-on-companion-libraries%3A\" tabindex=\"-1\">Note on Companion Libraries:</h4><p>Following the release of <a href=\"https://byteaether.github.io/2026/streamlining-net-data-layers-with-byteaetherulid-v140/\">ByteAether.Ulid v1.4.0</a>, official integration packages (including <a href=\"https://www.nuget.org/packages/ByteAether.Ulid.linq2db\" target=\"_blank\" rel=\"noopener noreferrer\"><code>ByteAether.Ulid.linq2db</code></a>) are now available to encapsulate ORM type conversions. While production applications can leverage <code>DataOptions.RegisterUlid()</code> to simplify setup, this repository retains the explicit mapping code inside <code>InitDataContext()</code>. Keeping this logic visible eliminates &quot;framework magic&quot; and clarifies the precise conversion mechanisms LinqToDB relies on behind the scenes.</p></blockquote><h3 id=\"dal.context.entity\" tabindex=\"-1\"><code>DAL.Context.Entity</code></h3><p>This namespace is where we will create additional partial classes to extend the functionality of our generated entity classes. For example, we could create a <code>Post.cs</code> file to add a convenience method for getting the post's user from the database.</p><h3 id=\"dal.scaffoldinterceptor\" tabindex=\"-1\"><code>DAL.ScaffoldInterceptor</code></h3><p>This is the &quot;secret sauce&quot; of our scaffolding process. We will create a class that implements <code>ScaffoldInterceptors</code>. This interceptor allows us to hook into the scaffolding process and customize it.</p><p>Our custom interceptor will perform two key functions:</p><ol><li><strong>Interface Injection</strong>: It will automatically add our <code>IEntity</code> and <code>IIdentifiable&lt;Ulid&gt;</code> interfaces to the generated partial entity classes.</li><li><strong>ULID Type Mapping</strong>: It will intercept any database column with the <code>ulid</code> type and ensure it is properly mapped to the <code>Ulid</code> type from our C# library in the generated code.</li><li><strong>Association Naming Convention</strong>: We will use the interceptor to enforce a strict and predictable naming convention for association properties. For One-To-Many relationships, we adopt a simple 's' suffix (e.g., a <code>Post</code> entity will have a property named <code>Comments</code>, not <code>Commentaries</code> or <code>Commentss</code>). This deliberate choice avoids the ambiguity and confusion that can arise from irregular pluralization in English (e.g., avoiding a scenario where a <code>Mouse</code> entity might generate a <code>Mice</code> collection). For Many-To-One relationships, we simply use the entity's singular name (e.g., a <code>Comment</code> entity will have a <code>Post</code> property).</li></ol><p>The use of partial classes is a core tenet of this approach. It gives us the best of both worlds: we have the convenience of an automatically generated code base that stays in sync with our database schema, while also having the freedom to extend and customize our models and database context without ever touching the generated code.</p><h2 id=\"looking-ahead\" tabindex=\"-1\">Looking Ahead</h2><p>In this post, we've defined the bedrock of our enterprise DAL, from the database schema and the choice of ULID primary keys to the core code structure and our enhanced scaffolding process. We started simple, without implementing audit fields or a permission system. The project's root also includes a simple <code>Program.cs</code> file used for demonstration. This utility file connects to our SQLite database and runs simple LINQ queries (like selecting all users). Crucially, we use it to inspect the <em>generated SQL</em> from our LINQ expressions, rather than executing the queries and reviewing the result set. This allows us to verify that our DAL is correctly translating our C# code into performant SQL.</p><p>The full source code for this post, including the scaffolding interceptor and the completed project structure, is available at <a href=\"https://github.com/ByteAether/EnterpriseDal/tree/part2\" target=\"_blank\" rel=\"noopener noreferrer\">GitHub</a>.</p><p>In the next post, we will dive into the details of implementing the crucial audit fields (<code>CreatedAt</code>, <code>CreatedBy</code>, <code>ModifiedAt</code>, and <code>ModifiedBy</code>) and the logic to automatically populate them during data operations. We'll show how the foundation we've built here makes this complex, cross-cutting concern remarkably simple to implement.</p>",
      "date_published": "2025-10-07T00:00:00Z"
    }
    ,
    {
      "id": "https://byteaether.github.io/2025/building-an-enterprise-data-access-layer-the-foundation/",
      "url": "https://byteaether.github.io/2025/building-an-enterprise-data-access-layer-the-foundation/",
      "title": "Building an Enterprise Data Access Layer: The Foundation",
      "content_html": "<p>In enterprise software development, the <strong>Data Access Layer (DAL)</strong> serves as the critical interface between an application's business logic and its underlying data store. Beyond basic CRUD operations, a robust DAL is fundamental for ensuring data integrity, security, and operational consistency. It proactively addresses cross-cutting concerns, preventing inconsistencies, defects, and potential security vulnerabilities that arise when these responsibilities are left to the business logic.</p><p>This series will detail the design and implementation of an enterprise-level Data Access Layer in C#, with a primary focus on <a href=\"https://github.com/linq2db/linq2db\" target=\"_blank\" rel=\"noopener noreferrer\">LinqToDB</a>. We'll demonstrate how to embed essential cross-cutting concerns directly within the DAL, ensuring their consistent application without reliance on manual enforcement within the business logic. LinqToDB's capabilities, particularly its strong support for advanced SQL features and expression tree manipulation, make it an excellent choice for building a highly customized and efficient DAL. We outline the fundamental capabilities such a DAL must provide, establishing the framework for subsequent practical implementations.</p><h2 id=\"the-necessity-of-an-automated%2C-error-resistant-dal\" tabindex=\"-1\">The Necessity of an Automated, Error-Resistant DAL</h2><p>The lifecycle of an enterprise application involves continuous data creation, modification, and retrieval by various users and automated processes. Without a centralized, opinionated, and highly capable DAL, developers are tasked with manually implementing critical data-related behaviors across numerous service methods and business logic components. This decentralized approach is susceptible to human error, such as overlooking a filter, omitting an audit field, or mismanaging a soft-delete operation. Such oversights can lead to:</p><ul><li><strong>Data Inconsistencies:</strong> Records may lack complete audit information, entities might be inconsistently soft-deleted, or multi-tenancy rules may be applied unevenly.</li><li><strong>Security Vulnerabilities:</strong> A missed <code>TenantId</code> filter or inadequate enforcement of row-level permissions can result in unauthorized data exposure. Such breaches carry significant legal, financial, and reputational risks.</li><li><strong>Increased Development Overhead and Technical Debt:</strong> Manual implementation of cross-cutting concerns introduces boilerplate code, extends development cycles, and increases the probability of introducing subtle, hard-to-diagnose defects.</li></ul><p>The core premise of this series is that these capabilities must be inherent to the DAL. The DAL should function as a guardian, automatically applying these rules and transformations, thereby offloading this responsibility from the business logic and ensuring consistent, secure, and reliable data interaction.</p><h2 id=\"core-capabilities-of-an-enterprise-data-access-layer\" tabindex=\"-1\">Core Capabilities of an Enterprise Data Access Layer</h2><p>We'll now examine the critical capabilities an enterprise DAL should provide, abstracting them from the application's business logic.</p><h3 id=\"1.-createdat%2Fmodifiedat-auditing\" tabindex=\"-1\">1. <code>CreatedAt</code>/<code>ModifiedAt</code> Auditing</h3><p>Maintaining an audit trail of when data records were created and last modified is a fundamental requirement for most enterprise applications. This temporal auditing provides insights for debugging, compliance, and understanding data evolution. Manually setting <code>CreatedAt</code> and <code>ModifiedAt</code> timestamps in the business logic for every <code>INSERT</code> and <code>UPDATE</code> operation is repetitive and prone to omission.</p><p>A sophisticated DAL should automate this process. Upon entity insertion, the DAL should automatically populate its <code>CreatedAt</code> property with the current timestamp. Similarly, upon any update to an existing entity, the DAL should automatically update its <code>ModifiedAt</code> property. This ensures that every record consistently carries its creation and last modification timestamps, without explicit action required from the business logic developer. This automation guarantees comprehensive temporal auditing across the entire dataset.</p><h3 id=\"2.-createdby%2Fmodifiedby-auditing\" tabindex=\"-1\">2. <code>CreatedBy</code>/<code>ModifiedBy</code> Auditing</h3><p>Extending temporal auditing, it is often critical to identify the user responsible for a particular data change. This &quot;who&quot; information, typically represented by a User ID, enhances accountability and traceability within the audit trail. As with <code>CreatedAt</code>/<code>ModifiedAt</code>, manually populating <code>CreatedBy</code> and <code>ModifiedBy</code> fields in the business logic is a source of potential errors.</p><p>The enterprise DAL should seamlessly manage this. For new entities, it should automatically set the <code>CreatedBy</code> property to the ID of the current user. For updates, it should update the <code>ModifiedBy</code> property. The current User ID must be settable for the scope of an entire API request or a specific unit of work. The DAL will then automatically retrieve and apply this contextual User ID to the appropriate fields during persistence operations. This mechanism ensures that every data change is attributed to the responsible user, providing a complete and reliable audit history.</p><h3 id=\"3.-soft-delete\" tabindex=\"-1\">3. Soft-Delete</h3><p>In many enterprise scenarios, physical data deletion is undesirable due to auditing requirements, data recovery needs, or the necessity of maintaining historical context. Soft-delete addresses this by marking a record as logically &quot;deleted&quot; rather than physically removing it.</p><p>An enterprise DAL should automate this process. When the business logic initiates a &quot;delete&quot; operation, the DAL should intercept this call. Instead of issuing a <code>DELETE</code> SQL command, it should update a designated <code>DeletedAt</code> property (e.g., a <code>DateTimeOffset?</code> nullable timestamp) on the entity. Furthermore, all standard read operations performed through the DAL must automatically filter out entities where <code>DeletedAt</code> is not null, unless explicitly overridden (e.g., for administrative recovery tools). This ensures that the business logic perceives soft-deleted entities as non-existent without requiring manual <code>WHERE DeletedAt IS NULL</code> clauses in every query. This capability is essential for data integrity and provides a safeguard against accidental data loss.</p><h3 id=\"4.-tenantid-filtering-(multi-tenancy)\" tabindex=\"-1\">4. <code>TenantId</code> Filtering (Multi-Tenancy)</h3><p>Multi-tenancy, where a single application instance serves multiple distinct organizations (tenants), is a common architectural pattern in SaaS solutions. Strict data isolation is critical: <code>Tenant A</code> must never access <code>Tenant B</code>'s data. Enforcing this isolation is exceptionally challenging if left to the business logic, as every query, join, and relationship traversal would require manual <code>TenantId</code> filtering. A single missed filter can lead to catastrophic data leakage.</p><p>Our enterprise DAL must provide automatic <code>TenantId</code> filtering. For entities designated as &quot;tenant-owned,&quot; the DAL should automatically inject a <code>WHERE TenantId = [CurrentTenantId]</code> clause into all read operations. Crucially, this filtering must propagate throughout all associations. If a query involves joining across multiple tables, the DAL must ensure the <code>TenantId</code> filter is applied appropriately at each relevant level to prevent data from other tenants from being inadvertently included. Similar to the <code>CreatedBy</code>/<code>ModifiedBy</code> capability, the <code>TenantId</code> must be settable for the scope of the current API request or unit of work, enabling the DAL to transparently apply it to all relevant data operations. This capability is a cornerstone of security and data isolation in multi-tenant architectures.</p><h3 id=\"5.-entity-based-permissions-(row-level-security)\" tabindex=\"-1\">5. Entity-Based-Permissions (Row-Level Security)</h3><p>Beyond multi-tenancy, many applications require more granular control over data visibility, often termed row-level security or entity-based permissions. This allows specific users to view certain entities while restricting others, even within the same tenant. For example, in a project management system, a user might only access tasks assigned to them or their team. Implementing this within the business logic is complex, often resulting in deeply nested conditional logic and a high risk of oversight.</p><p>An advanced DAL should fully enforce entity-based permissions. Entities themselves will define whether they are protected by the permission system. For protected entities, they will also define how the ID, upon which the permission system will operate, is derived. The DAL will then automatically apply these permission checks during all read operations, effectively filtering out rows that the current user is not authorized to see. This abstraction ensures that the business logic operates under the assumption that it only receives data the user is permitted to view, significantly simplifying development and bolstering security. The current User ID, set for the scope of the API request or unit of work, will provide the context for these permission evaluations.</p><h3 id=\"6.-projected-data-support-for-contextual-filtering\" tabindex=\"-1\">6. Projected Data Support for Contextual Filtering</h3><p>Both <code>TenantId</code> filtering and entity-based permissions introduce a common challenge: the relevant ID (e.g., <code>TenantId</code> or permission <code>ObjectId</code>) may not always be a direct property on the entity being queried. For instance, a <code>Comment</code> entity might not have its own <code>TenantId</code> but instead inherits it from its parent <code>Post</code> entity (<code>this.Post.TenantId</code>). Similarly, permissions for a <code>Task</code> might be based on the <code>Id</code> of its <code>Project</code> (<code>this.Project.Id</code>).</p><p>The enterprise DAL must provide a mechanism for entities to define how these contextual IDs are retrieved. This &quot;projected data support&quot; allows an entity to specify a path or a function (e.g., a LINQ expression) that the DAL can use to resolve the <code>TenantId</code> or the <code>ObjectId</code> for filtering purposes. This flexibility ensures that the DAL can correctly apply multi-tenancy and row-level security even when the relevant identifiers are not directly present on the entity itself but are accessible through its relationships, maintaining consistency and correctness across complex data models.</p><h2 id=\"why-linqtodb%3F\" tabindex=\"-1\">Why LinqToDB?</h2><p>While <strong>Entity Framework Core (EF Core)</strong> is a widely adopted and powerful ORM in the .NET ecosystem, this series will primarily focus on implementing our enterprise DAL using <strong><a href=\"https://github.com/linq2db/linq2db\" target=\"_blank\" rel=\"noopener noreferrer\">LinqToDB</a></strong>. LinqToDB offers a highly flexible and performant approach to data access, particularly excelling in scenarios requiring fine-grained control over generated SQL.</p><p>One of LinqToDB's significant advantages for our purposes is its robust support for features like projected properties and direct translation of SQL window functions into LINQ queries. While not all of these advanced features may be strictly necessary for the current enterprise DAL implementation, they collectively illustrate LinqToDB's broader capabilities and its suitability for complex data manipulation. Such capabilities are crucial for efficiently implementing complex filtering and auditing mechanisms directly within the DAL, often requiring less boilerplate or specialized extension libraries compared to EF Core. While EF Core does support some of these advanced scenarios through community-contributed extension libraries, LinqToDB provides many of these features out-of-the-box, simplifying our implementation journey.</p><p>Our decision to focus on LinqToDB for this series is driven by its suitability for demonstrating how to build a highly optimized and feature-rich DAL that minimizes manual intervention and maximizes precision in data operations.</p><h2 id=\"scope-of-history-support\" tabindex=\"-1\">Scope of History Support</h2><p>It is important to clarify that while this series will implement certain auditing functionalities, such as <code>CreatedAt</code>/<code>ModifiedAt</code> and <code>CreatedBy</code>/<code>ModifiedBy</code> fields, it will <strong>not</strong> cover full history support. Implementing a complete archive of all data at all points in time, including granular versioning and temporal querying capabilities, is a significant undertaking. However, the capabilities developed in this series are foundational and can be extended with full history support later without invalidating the core principles and implementations discussed. Our focus remains on the automated enforcement of the capabilities outlined above to enhance data consistency and security at the transactional level.</p><h2 id=\"the-journey-ahead\" tabindex=\"-1\">The Journey Ahead</h2><p>Centralizing these essential capabilities within an enterprise-grade Data Access Layer is key to building applications that are inherently more consistent, secure, and maintainable. With LinqToDB as our primary ORM for this implementation journey, the business logic can focus solely on its core rules rather than on data access mechanics.</p><p>We encourage you to prepare your development environment for the next installment, where we will establish the foundational C# project and begin implementing the DAL with LinqToDB. Upon completion of this series, a comparative analysis may also be provided by reproducing the same solution using Entity Framework Core.</p>",
      "date_published": "2025-09-25T00:00:00Z"
    }
    ,
    {
      "id": "https://byteaether.github.io/2025/byteaetherulid-v130-enhanced-ulid-generation-control-and-security/",
      "url": "https://byteaether.github.io/2025/byteaetherulid-v130-enhanced-ulid-generation-control-and-security/",
      "title": "ByteAether.Ulid v1.3.0: Enhanced ULID Generation Control and Security",
      "content_html": "<p><strong><a href=\"https://github.com/ByteAether/Ulid/releases/tag/v1.3.0\" target=\"_blank\" rel=\"noopener noreferrer\">ByteAether.Ulid v1.3.0</a></strong>, a significant update to our high-performance .NET ULID implementation, has been released. This version introduces enhanced flexibility and security for ULID generation, reinforcing its suitability for modern applications.</p><p>ByteAether aims to provide a compliant and dependable ULID solution for .NET developers. ULIDs combine universal uniqueness with lexicographical sortability, making them suitable for distributed systems and time-ordered data by optimizing indexing and querying.</p><h2 id=\"enhanced-generation-control-with-generationoptions\" tabindex=\"-1\">Enhanced Generation Control with <code>GenerationOptions</code></h2><p>Previous versions of ByteAether.Ulid utilized a <code>bool? isMonotonic</code> and <code>Ulid.DefaultIsMonotonic</code> flags for monotonic generation control. Version 1.3.0 replaces this with a new configuration object, <code>GenerationOptions</code>, offering granular control over ULID generation. <code>GenerationOptions</code> includes three parameters: <code>Monotonicity</code>, <code>InitialRandomSource</code>, and <code>IncrementRandomSource</code>.</p><h2 id=\"generationoptions-explained\" tabindex=\"-1\"><code>GenerationOptions</code> Explained</h2><p>Let's explore the new <code>GenerationOptions</code> in detail:</p><h3 id=\"monotonicity\" tabindex=\"-1\"><code>Monotonicity</code></h3><p>This option controls the monotonic behavior of ULIDs, offering choices to balance predictability and uniqueness:</p><ul><li><strong><code>NonMonotonic</code></strong><br/>Generates a fully random <code>Random</code> component, suitable when strict sequential ordering is not required.</li><li><strong><code>MonotonicIncrement</code> (Default)</strong><br/>Maintains strict monotonic progression by incrementing the random portion of the ULID by 1, mirroring previous monotonic generation behavior.</li><li><strong><code>MonotonicRandom1Byte</code></strong></li><li><strong><code>MonotonicRandom2Byte</code></strong></li><li><strong><code>MonotonicRandom3Byte</code></strong></li><li><strong><code>MonotonicRandom4Byte</code></strong><br/>These options enhance security and unpredictability. As discussed in <a href=\"https://github.com/ulid/spec/issues/105\" target=\"_blank\" rel=\"noopener noreferrer\">ULID specification issue #105</a>, incrementing the random part by <code>1</code> can lead to predictable ULIDs within the same millisecond, potentially enabling enumeration attacks. To mitigate this, these options allow configuring a random increment from 1-byte (1–256) to 4-byte (1–4,294,967,296) range using the <code>IncrementRandomSource</code>. This adds a layer of randomness, making ULIDs more difficult to guess while preserving lexicographical sortability, directly addressing enumeration mitigation concerns in the ULID specification.</li></ul><h3 id=\"initialrandomsource-and-incrementrandomsource\" tabindex=\"-1\"><code>InitialRandomSource</code> and <code>IncrementRandomSource</code></h3><p>Both <code>InitialRandomSource</code> and <code>IncrementRandomSource</code> properties are of type <code>IRandomProvider</code>. This design provides a flexible way to define how randomness is produced, <strong>allowing users to implement and provide their own custom random number generators</strong>.</p><p>The <code>InitialRandomSource</code> option specifies the source of randomness for the initial random component of the ULID. The <code>IncrementRandomSource</code> setting determines the source of randomness for the increment value when using <code>MonotonicRandomXByte</code> options for monotonicity.</p><ul><li><strong><code>CryptographicallySecureRandomProvider</code> (Default for <code>InitialRandomSource</code>)</strong><br/>Utilizes a cryptographically secure random number generator, ensuring high entropy and suitability for security-sensitive applications.</li><li><strong><code>PseudoRandomProvider</code> (Default for <code>IncrementRandomSource</code>)</strong><br/>Employs a faster pseudo-random algorithm, suitable for scenarios where cryptographic security isn't a primary concern, and performance is prioritized.</li></ul><h2 id=\"default-behavior-and-backward-compatibility\" tabindex=\"-1\">Default Behavior and Backward Compatibility</h2><p>Version 1.3.0's default settings ensure a seamless transition, matching previous ByteAether.Ulid monotonic generation behavior:</p><pre class=\"language-csharp\"><code class=\"language-csharp\">Ulid<span class=\"token punctuation\">.</span>DefaultGenerationOptions <span class=\"token operator\">=</span> <span class=\"token keyword\">new</span> <span class=\"token constructor-invocation class-name\">Ulid<span class=\"token punctuation\">.</span>GenerationOptions</span>\n<span class=\"token punctuation\">{</span>\n\tMonotonicity <span class=\"token operator\">=</span> MonotonicityOptions<span class=\"token punctuation\">.</span>MonotonicIncrement<span class=\"token punctuation\">,</span>\n\tInitialRandomSource <span class=\"token operator\">=</span> <span class=\"token keyword\">new</span> <span class=\"token constructor-invocation class-name\">CryptographicallySecureRandomProvider</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">,</span>\n\tIncrementRandomSource <span class=\"token operator\">=</span> <span class=\"token keyword\">new</span> <span class=\"token constructor-invocation class-name\">PseudoRandomProvider</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span>\n<span class=\"token punctuation\">}</span></code></pre><p>Upgrading to v1.3.0 will not alter existing ULID generation logic unless <code>GenerationOptions</code> are explicitly configured and assigned to <code>Ulid.DefaultGenerationOptions</code>.</p><p>For backward compatibility, public API methods with a <code>bool? isMonotonic</code> parameter and the <code>Ulid.DefaultIsMonotonic</code> static property remain, but are marked <code>[Obsolete]</code>. These are deprecated to encourage migration to the <code>GenerationOptions</code>-based methods for enhanced control and flexibility. Obsolete methods and properties will be removed in a future version and users are encouraged to update their code for future compatibility and to leverage new capabilities.</p><h2 id=\"why-ulids%3F\" tabindex=\"-1\">Why ULIDs?</h2><p>Identifier selection is crucial in distributed systems. GUIDs provide uniqueness but lack sortability, hindering database indexing and querying. Integer IDs are sortable but may conflict in distributed environments due to their limited scope.</p><p>ULIDs combine universal uniqueness with lexicographical sortability:</p><ul><li><strong>Universally Unique</strong>: Ensures global distinctness across systems.</li><li><strong>Lexicographically Sortable</strong>: Enables efficient time-based sorting for database performance and chronological data retrieval.</li><li><strong>Human-Readable</strong>: More manageable than GUIDs.</li></ul><p>ByteAether.Ulid is a robust and compliant .NET implementation. It addresses potential issues from the <a href=\"https://github.com/ulid/spec/issues/39\" target=\"_blank\" rel=\"noopener noreferrer\">official ULID specification, such as the &quot;random part overflow&quot; issue</a>, by allowing timestamp increments to ensure unique ULIDs even at high generation rates.</p><p><a href=\"https://github.com/ByteAether/Ulid?tab=readme-ov-file#benchmarking\" target=\"_blank\" rel=\"noopener noreferrer\">Benchmarking against other .NET ULID implementations</a> (e.g., <code>NetUlid</code>, <code>Ulid</code>, <code>NUlid</code>), <code>Guid</code>, and <code>GuidV7</code> consistently shows ByteAether.Ulid as highly performant and fully compliant with the ULID specification, ensuring dependable generation.</p><h2 id=\"installation-and-usage\" tabindex=\"-1\">Installation and Usage</h2><p>Install ByteAether.Ulid v1.3.0 via NuGet:</p><pre class=\"language-sh\"><code class=\"language-sh\">dotnet <span class=\"token function\">add</span> package ByteAether.Ulid</code></pre><p>Example of <code>GenerationOptions</code> usage:</p><pre class=\"language-csharp\"><code class=\"language-csharp\"><span class=\"token keyword\">using</span> <span class=\"token namespace\">ByteAether<span class=\"token punctuation\">.</span>Ulid</span><span class=\"token punctuation\">;</span>\n<span class=\"token keyword\">using</span> <span class=\"token keyword\">static</span> <span class=\"token class-name\">ByteAether<span class=\"token punctuation\">.</span>Ulid<span class=\"token punctuation\">.</span>Ulid<span class=\"token punctuation\">.</span>GenerationOptions</span><span class=\"token punctuation\">;</span>\n\n<span class=\"token class-name\"><span class=\"token keyword\">var</span></span> optionsWithRandomIncrement <span class=\"token operator\">=</span> <span class=\"token keyword\">new</span> <span class=\"token constructor-invocation class-name\">Ulid<span class=\"token punctuation\">.</span>GenerationOptions</span>\n<span class=\"token punctuation\">{</span>\n\t<span class=\"token comment\">// 4-byte random increment</span>\n\tMonotonicity <span class=\"token operator\">=</span> MonotonicityOptions<span class=\"token punctuation\">.</span>MonotonicRandom4Byte<span class=\"token punctuation\">,</span>\n\t<span class=\"token comment\">// Cryptographically secure initial random</span>\n\tInitialRandomSource <span class=\"token operator\">=</span> <span class=\"token keyword\">new</span> <span class=\"token constructor-invocation class-name\">CryptographicallySecureRandomProvider</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">,</span>\n\t<span class=\"token comment\">// Cryptographically secure increment random</span>\n\tIncrementRandomSource <span class=\"token operator\">=</span> <span class=\"token keyword\">new</span> <span class=\"token constructor-invocation class-name\">CryptographicallySecureRandomProvider</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span>\n<span class=\"token punctuation\">}</span><span class=\"token punctuation\">;</span>\n\n<span class=\"token class-name\"><span class=\"token keyword\">var</span></span> ulidWithRandomInc <span class=\"token operator\">=</span> Ulid<span class=\"token punctuation\">.</span><span class=\"token function\">New</span><span class=\"token punctuation\">(</span>optionsWithRandomIncrement<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\nConsole<span class=\"token punctuation\">.</span><span class=\"token function\">WriteLine</span><span class=\"token punctuation\">(</span><span class=\"token interpolation-string\"><span class=\"token string\">$\"ULID with random increment: </span><span class=\"token interpolation\"><span class=\"token punctuation\">{</span><span class=\"token expression language-csharp\">ulidWithRandomInc</span><span class=\"token punctuation\">}</span></span><span class=\"token string\">\"</span></span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\n<span class=\"token comment\">// Set default generation options globally</span>\nUlid<span class=\"token punctuation\">.</span>DefaultGenerationOptions <span class=\"token operator\">=</span> <span class=\"token keyword\">new</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span>\n<span class=\"token punctuation\">{</span>\n\tMonotonicity <span class=\"token operator\">=</span> MonotonicityOptions<span class=\"token punctuation\">.</span>MonotonicIncrement<span class=\"token punctuation\">,</span>\n\t<span class=\"token comment\">// Pseudo-randomness for speed</span>\n\tInitialRandomSource <span class=\"token operator\">=</span> <span class=\"token keyword\">new</span> <span class=\"token constructor-invocation class-name\">PseudoRandomProvider</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">,</span>\n\t<span class=\"token comment\">// IncrementRandomSource is not used by MonotonicIncrement</span>\n<span class=\"token punctuation\">}</span><span class=\"token punctuation\">;</span>\n\n<span class=\"token class-name\"><span class=\"token keyword\">var</span></span> defaultUlid <span class=\"token operator\">=</span> Ulid<span class=\"token punctuation\">.</span><span class=\"token function\">New</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\nConsole<span class=\"token punctuation\">.</span><span class=\"token function\">WriteLine</span><span class=\"token punctuation\">(</span><span class=\"token interpolation-string\"><span class=\"token string\">$\"ULID from default pseudo-random source: </span><span class=\"token interpolation\"><span class=\"token punctuation\">{</span><span class=\"token expression language-csharp\">defaultUlid</span><span class=\"token punctuation\">}</span></span><span class=\"token string\">\"</span></span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span></code></pre><h2 id=\"ecosystem-integration\" tabindex=\"-1\">Ecosystem Integration</h2><p>ByteAether.Ulid integrates with popular .NET libraries and frameworks:</p><ul><li><strong>ASP.NET Core</strong>: Integrates as a route or query parameter with a built-in <code>TypeConverter</code>.</li><li><strong>System.Text.Json (.NET 5.0+)</strong>: Includes a <code>JsonConverter</code> for serialization and deserialization.</li><li><strong>EF Core</strong>: Supports ULIDs as primary keys or properties via a custom <code>ValueConverter</code> (<code>Ulid</code> to <code>byte[]</code>).</li><li><strong>Dapper</strong>: Supports custom <code>TypeHandler</code> for <code>Ulid</code> to <code>byte[]</code> conversion.</li><li><strong>MessagePack</strong>: Provides a custom <code>MessagePackResolver</code> for Ulid (as <code>byte[]</code>) serialization and deserialization.</li><li><strong>Newtonsoft.Json</strong>: A custom <code>JsonConverter</code> enables Ulid (as <code>string</code>) serialization and deserialization.</li></ul><h2 id=\"contribute-to-byteaether.ulid\" tabindex=\"-1\">Contribute to ByteAether.Ulid</h2><p>We are committed to the continuous improvement of <a href=\"https://github.com/ByteAether/Ulid\" target=\"_blank\" rel=\"noopener noreferrer\">ByteAether.Ulid</a>. Contributions, including pull requests, bug reports, and feature suggestions, are welcome. Your input is valuable for shaping the project's future.</p><p>Upgrade to <strong>ByteAether.Ulid v1.3.0</strong> for enhanced control, security, and performance in ULID generation.</p>",
      "date_published": "2025-07-31T00:00:00Z"
    }
    ,
    {
      "id": "https://byteaether.github.io/2025/prioritizing-reliability-when-milliseconds-arent-enough/",
      "url": "https://byteaether.github.io/2025/prioritizing-reliability-when-milliseconds-arent-enough/",
      "title": "Prioritizing Reliability When Milliseconds Aren&#39;t Enough",
      "content_html": "<p>Universally Unique Lexicographically Sortable Identifiers (ULIDs) represent a significant advancement over traditional UUIDs, offering guaranteed uniqueness (within practical limits), natural sortability by time, and a concise, URL-friendly format. These features make them highly suitable for diverse applications, including database keys, distributed tracing, and event identifiers. While the intricacies of ID generation might seem like a deep concern primarily for high-throughput systems, understanding the underlying design choices is valuable for all developers leveraging such libraries.</p><p>A standard ULID consists of a 48-bit timestamp (milliseconds since the Unix epoch) and an 80-bit random component. This structure ensures IDs generally sort chronologically. However, the official specification includes a mechanism to handle multiple IDs generated within the <em>exact same millisecond</em>: it increments the 80-bit random part sequentially. This leads to a critical question: What happens if this increment causes the 80-bit random part to exceed its maximum value?</p><p><a href=\"https://github.com/ulid/spec\" target=\"_blank\" rel=\"noopener noreferrer\">The official specification</a> mandates throwing an overflow exception in this situation. While this adheres strictly to the definition, it introduces a potential point of failure, especially in high-performance systems. Crucially, this isn't just a theoretical concern that requires generating 2^80 IDs. For the <em>first</em> ULID generated within a specific millisecond, the 80-bit portion is initialized with a cryptographically random value. If this initial random value happens to be very close to the maximum possible value (all 1's in binary), even the <em>second</em> ID generated within that same millisecond could trigger the increment overflow and, consequently, an exception. This makes the overflow a far more probable scenario than one might initially assume based purely on the size of the 80-bit space.</p><p>In developing <strong><a href=\"https://byteaether.github.io/series/byteaether-ulid/\">ByteAether.Ulid</a></strong>, our C# implementation available on <a href=\"https://github.com/ByteAether/Ulid\" target=\"_blank\" rel=\"noopener noreferrer\">GitHub</a> and <a href=\"https://www.nuget.org/packages/ByteAether.Ulid\" target=\"_blank\" rel=\"noopener noreferrer\">NuGet</a>, we recognized the practical implications of this potential exception. We made a deliberate design choice, a documented deviation from the strict specification, to enhance dependability: instead of throwing an exception, <strong>ByteAether.Ulid</strong> allows the increment operation to <em>flow into the 48-bit timestamp part</em> when the 80-bit random part overflows. This post explains the rationale behind this decision.</p><h2 id=\"the-overflow-scenario%3A-more-likely-than-you-think\" tabindex=\"-1\">The Overflow Scenario: More Likely Than You Think</h2><p>Consider systems that demand high throughput: real-time analytics pipelines, busy web servers handling rapid requests, IoT data ingestion points, or batch processing jobs churning through records. In these environments, generating multiple unique identifiers within a single millisecond is commonplace.</p><p>The ULID specification's approach ensures monotonicity <em>within</em> a millisecond by incrementing the random part. For example:</p><ol><li><code>Ulid.New()</code> at <code>T</code> ms: <code>[Timestamp T | Random Value R1]</code></li><li><code>Ulid.New()</code> at <code>T</code> ms again: <code>[Timestamp T | Random Value R1 + 1]</code></li><li><code>Ulid.New()</code> at <code>T</code> ms yet again: <code>[Timestamp T | Random Value R1 + 2]</code> ... and so on.</li></ol><p>The issue arises when <code>Random Value R1 + n</code> reaches the maximum value representable by 80 bits. As noted earlier, if the initial <code>Random Value R1</code> generated by the secure random number generator is already near this maximum, it might only take one or two subsequent calls to <code>Ulid.New()</code> within the same millisecond to hit the ceiling, if the initial random draw was exceptionally unlucky (i.e., very close to the maximum).</p><p>An implementation strictly following the spec would then throw an exception. This creates significant challenges:</p><ol><li><strong>Reduced Reliability:</strong> ID generation, a typically background concern, becomes a potential source of runtime failure, especially under peak load conditions where reliability is most critical.</li><li><strong>Increased Complexity &amp; Performance Impact:</strong> Developers must anticipate and handle these exceptions. <code>try-catch</code> blocks, particularly when an exception is actually thrown, can incur a non-trivial performance cost, especially in very hot code paths like ID generation. This might lead to implementing retry logic or other workarounds that add complexity and can negatively impact the very performance ULIDs are often chosen to support.</li></ol><h3 id=\"update%3A-the-impact-of-random-increments-on-overflow-probability\" tabindex=\"-1\">Update: The Impact of Random Increments on Overflow Probability</h3><p>With the release of <strong><a href=\"https://byteaether.github.io/2025/byteaetherulid-v130-enhanced-ulid-generation-control-and-security/\">ByteAether.Ulid v1.3.0</a></strong>, our decision to handle random-part overflow by incrementing the timestamp has become even more critical and relevant. The previous monotonic generation behavior, which incremented the random part by a fixed value of 1, made the overflow scenario more likely than one might assume, especially if the initial random value was close to the maximum.</p><p>The new <strong><code>GenerationOptions</code></strong> configuration object introduces several <code>MonotonicRandomXByte</code> options, which enhance security by allowing the increment value to be a random number. This value can range from 1 to 256 for <code>MonotonicRandom1Byte</code>, up to 1 to 4 294 967 296 for <code>MonotonicRandom4Byte</code>. While this adds an important layer of security against enumeration attacks, it also significantly increases the probability of an 80-bit random part overflow occurring within a single millisecond.</p><p>Consider a scenario where <code>MonotonicRandom4Byte</code> is used. A cryptographically secure random provider could generate an initial 80-bit random component close to the maximum value, and the very next ULID generated in that same millisecond could have an increment value of 2^32, causing an immediate overflow.</p><p>Our approach of allowing the increment to flow into the timestamp directly addresses this increased risk. Instead of a predictable, linear march toward a potential exception, the new random increments make the overflow threshold far more volatile. By not throwing an exception and instead seamlessly incrementing the timestamp, our implementation ensures continued, reliable operation under these new, more complex generation scenarios, reinforcing its value for robust and high-throughput systems.</p><h2 id=\"considering-the-alternatives\" tabindex=\"-1\">Considering the Alternatives</h2><p>When faced with the random-part overflow, beyond throwing an exception (as per the spec), several strategies exist:</p><ol><li><p><strong>Delay Generation:</strong> This approach involves pausing the generation thread until the system clock advances to the next millisecond. While ensuring the timestamp strictly reflects the wall-clock time (or a later one), it introduces artificial latency. Halting execution, even briefly, can create significant bottlenecks in high-throughput systems, effectively throttling the application's performance based on clock resolution rather than processing capability. This often defeats the purpose of using efficient ID generation.</p></li><li><p><strong>Force Initial MSB to Zero:</strong> This technique modifies the generation of the <em>initial</em> random value (<code>R1</code> in the example above) within a millisecond. The most significant bit (MSB) of the 80-bit random part is always set to 0. This effectively means the initial random value is always less than 2^79. Consequently, at least 2^79 increments are guaranteed to be possible within that millisecond before the 80-bit value overflows.</p><ul><li><strong>Pros:</strong> Drastically reduces the <em>probability</em> of encountering an overflow, as 2^79 increments within a single millisecond is practically impossible on current hardware. It eliminates the &quot;bad luck&quot; scenario where the first random value is already near the maximum.</li><li><strong>Cons:</strong> It doesn't technically <em>eliminate</em> the overflow problem, it just pushes the threshold extremely high. A decision is <em>still</em> required for what to do if that 2^79 limit is somehow reached (throw, delay, or increment timestamp). It also slightly reduces the initial entropy from 80 bits to 79 bits, though this effect on collision probability is generally negligible. Fundamentally, it mitigates the likelihood but not the existence of the overflow condition itself.</li></ul></li><li><p><strong>Allow Overflow into Timestamp:</strong> This is the strategy implemented in <strong>ByteAether.Ulid</strong>. When the 80-bit random part reaches its limit and needs to increment, the increment operation carries over, increasing the 48-bit timestamp component by one millisecond. The practical effect is that the generated ULID might have a timestamp slightly &quot;ahead&quot; of the precise system clock time at the moment of generation.</p></li></ol><p>Comparing these, the <strong>ByteAether.Ulid</strong> approach directly addresses the <em>consequence</em> of the overflow by providing a non-blocking, non-exception-throwing resolution, regardless of how likely the overflow is. We contend that this provides the optimal blend of performance, reliability, and practical sortability for demanding applications.</p><h2 id=\"understanding-the-impact%3A-monotonicity-in-practice\" tabindex=\"-1\">Understanding the Impact: Monotonicity in Practice</h2><p>A natural question regarding the timestamp overflow approach is its effect on ULID's sortability and monotonicity guarantees. It's essential to analyze this within realistic contexts.</p><p><strong>Single-Process Monotonicity is Strictly Preserved:</strong> This is the cornerstone of the argument. Within a single running process, <strong>ByteAether.Ulid</strong> <em>guarantees</em> that each call to <code>Ulid.New()</code> produces a ULID value strictly greater than the previous one. Whether the increment occurs within the 80-bit random part or spills over into the 48-bit timestamp part, the resulting 128-bit value increases. This ensures that IDs generated sequentially <em>by the same generator instance</em> remain perfectly sortable relative to each other. This is often the most critical monotonicity requirement for use cases like database record ordering within an import batch.</p><p><strong>Cross-Process Monotonicity is Inherently Approximate:</strong> Attempting to achieve strict, guaranteed millisecond-level chronological ordering using ULIDs <em>across different machines or even different processes on the same machine</em> is inherently unreliable, irrespective of how overflow is handled. Factors like clock skew, network latency, and scheduling jitter mean you cannot reliably assert that <code>ULID_A</code> generated on <code>Machine_1</code> at its perceived time <code>T</code> is strictly less than <code>ULID_B</code> generated on <code>Machine_2</code> at its perceived time <code>T + 1ms</code>.</p><p>Given this reality, the fact that <strong>ByteAether.Ulid</strong> might, in high-contention scenarios, advance the timestamp by a millisecond due to overflow does not practically diminish the utility of ULIDs for cross-process chronological estimation. The timestamp remains a highly accurate representation of the generation time. Crucially, this &quot;future-drift&quot; is typically only by a single millisecond; once the timestamp increments, the subsequent ULID generated in this &quot;new&quot; millisecond has the full 2^80 range for its random component, making further immediate overflows into the timestamp exceptionally unlikely. This minimal adjustment is well within the margin of error already introduced by system clocks and network effects, and it prevents generation failure without meaningfully impacting the approximate sortability expected in distributed contexts.</p><p><strong>Alternative - Centralized ID Generation:</strong> One could achieve stronger cross-instance monotonicity by funneling all ID requests through a dedicated, single-instance &quot;ID Generator Service.&quot; This service could maintain strict ordering. However, this introduces significant drawbacks: primarily, <strong>network latency</strong> for every ID request and a <strong>single point of failure/bottleneck</strong>. This contrasts sharply with the benefit of ULIDs: fast, decentralized, in-process generation. For systems prioritizing low-latency generation, the centralized approach is often impractical.</p><p>In summary, <strong>ByteAether.Ulid</strong>'s approach trades theoretical timestamp purity (which is already compromised in distributed systems) for practical reliability and performance, while fully preserving the critical single-process monotonicity guarantee.</p><h2 id=\"why-reliable-monotonicity-matters%3A-the-data-import-example\" tabindex=\"-1\">Why Reliable Monotonicity Matters: The Data Import Example</h2><p>Let's revisit the large-scale data import workflow to illustrate the value of <strong>ByteAether.Ulid</strong>'s reliable single-process monotonicity. A single process reads millions of source records, transforms them, assigns a ULID, and inserts them into a database.</p><pre class=\"language-csharp\"><code class=\"language-csharp\"><span class=\"token comment\">// Simplified Example using ByteAether.Ulid</span>\n<span class=\"token keyword\">public</span> <span class=\"token keyword\">class</span> <span class=\"token class-name\">DataImporter</span>\n<span class=\"token punctuation\">{</span>\n\t<span class=\"token keyword\">private</span> <span class=\"token keyword\">readonly</span> <span class=\"token class-name\">IDatabaseRepository</span> _repository<span class=\"token punctuation\">;</span>\n\t<span class=\"token comment\">// ... constructor ...</span>\n\n\t<span class=\"token keyword\">public</span> <span class=\"token return-type class-name\"><span class=\"token keyword\">void</span></span> <span class=\"token function\">ImportData</span><span class=\"token punctuation\">(</span><span class=\"token class-name\">IEnumerable<span class=\"token punctuation\">&lt;</span>SourceRecord<span class=\"token punctuation\">></span></span> sourceRecords<span class=\"token punctuation\">)</span>\n\t<span class=\"token punctuation\">{</span>\n\t\t<span class=\"token class-name\"><span class=\"token keyword\">var</span></span> recordsToInsert <span class=\"token operator\">=</span> <span class=\"token keyword\">new</span> <span class=\"token constructor-invocation class-name\">List<span class=\"token punctuation\">&lt;</span>TargetRecord<span class=\"token punctuation\">></span></span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\t\t<span class=\"token keyword\">foreach</span> <span class=\"token punctuation\">(</span><span class=\"token class-name\"><span class=\"token keyword\">var</span></span> sourceRecord <span class=\"token keyword\">in</span> sourceRecords<span class=\"token punctuation\">)</span>\n\t\t<span class=\"token punctuation\">{</span>\n\t\t\t<span class=\"token comment\">// ByteAether.Ulid handles potential same-ms overflow gracefully</span>\n\t\t\t<span class=\"token comment\">// Potential high-frequency generation here!</span>\n\t\t\t<span class=\"token class-name\">Ulid</span> newId <span class=\"token operator\">=</span> Ulid<span class=\"token punctuation\">.</span><span class=\"token function\">New</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\n\t\t\t<span class=\"token class-name\"><span class=\"token keyword\">var</span></span> targetRecord <span class=\"token operator\">=</span> <span class=\"token keyword\">new</span> <span class=\"token constructor-invocation class-name\">TargetRecord</span> <span class=\"token punctuation\">{</span>\n\t\t\t\tId <span class=\"token operator\">=</span> newId<span class=\"token punctuation\">,</span>\n\t\t\t\t<span class=\"token comment\">/* ... other fields ... */</span>\n\t\t\t<span class=\"token punctuation\">}</span><span class=\"token punctuation\">;</span>\n\t\t\trecordsToInsert<span class=\"token punctuation\">.</span><span class=\"token function\">Add</span><span class=\"token punctuation\">(</span>targetRecord<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\t\t<span class=\"token punctuation\">}</span>\n\t\t_repository<span class=\"token punctuation\">.</span><span class=\"token function\">BulkInsert</span><span class=\"token punctuation\">(</span>recordsToInsert<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\t<span class=\"token punctuation\">}</span>\n\t<span class=\"token comment\">// ... other methods and record definitions ...</span>\n<span class=\"token punctuation\">}</span></code></pre><p><strong>Advantages in this Scenario:</strong></p><ol><li><strong>Dependable High-Speed Operation:</strong> The import can run at full speed. Even if bursts occur where the random part of the ULID would overflow (potentially after just one previous generation in the same millisecond if the initial random value was high), <strong>ByteAether.Ulid</strong> seamlessly increments the timestamp and continues generation without throwing exceptions. This prevents job failures and avoids the need for complex error handling around <code>Ulid.New()</code>.</li><li><strong>Preserved Processing Order:</strong> Because each <code>newId</code> is guaranteed to be greater than the last within this import process, the generated IDs maintain the sequence in which records were handled. If used as a clustered primary key, this aids database insertion performance and allows efficient time-based range queries on the imported batch. The sequence is inherent in the IDs themselves.</li><li><strong>No Artificial Bottlenecks:</strong> Unlike a delay-based approach, <strong>ByteAether.Ulid</strong> doesn't artificially throttle the import speed based on millisecond clock ticks, allowing the system to utilize its full processing power.</li></ol><p>Using an implementation that throws exceptions could lead to sporadic, hard-to-reproduce failures during large imports. Using one that delays would unnecessarily extend the import duration. Using the MSB=0 approach would make failures <em>extremely</em> unlikely, but still theoretically possible, requiring a fallback strategy (like throwing or delaying anyway) that doesn't offer the same definitive resolution. <strong>ByteAether.Ulid</strong> provides a direct, reliable handling mechanism.</p><h2 id=\"a-pragmatic-choice-for-robust-systems\" tabindex=\"-1\">A Pragmatic Choice for Robust Systems</h2><p>The decision in <strong>ByteAether.Ulid</strong> to handle random-part overflow by incrementing the timestamp is a pragmatic one, driven by the need for reliability and performance in real-world applications. It recognizes that the overflow scenario is more likely than a simple 2^80 calculation suggests due to the random starting point within each millisecond.</p><p>This approach:</p><ul><li>Ensures <strong>dependability</strong> by eliminating a source of exceptions in high-throughput scenarios.</li><li>Maintains <strong>performance</strong> by avoiding the latency introduced by delay-based strategies and the overhead of frequent exception handling.</li><li>Guarantees <strong>strict single-process monotonicity</strong>, preserving the most reliable and often most crucial sortability aspect of ULIDs.</li><li>Acknowledges that <strong>cross-process millisecond ordering</strong> is already approximate, making the minor, typically single-millisecond timestamp adjustment during overflow insignificant in that context.</li><li>Provides a <strong>definitive handling mechanism</strong>, unlike approaches that merely reduce the <em>likelihood</em> of overflow without specifying the ultimate fallback.</li></ul><p>While representing a documented deviation from the specification's exception-throwing behavior, this design choice prioritizes building robust, performant systems that can handle demanding workloads without faltering. We believe this makes <strong><a href=\"https://github.com/ByteAether/Ulid\" target=\"_blank\" rel=\"noopener noreferrer\">ByteAether.Ulid</a></strong> a strong choice for developers needing fast, reliable, and sortable unique identifiers in C#.</p>",
      "date_published": "2025-06-19T00:00:00Z"
    }
    ,
    {
      "id": "https://byteaether.github.io/2025/announcing-byteaetherweakevent-v100/",
      "url": "https://byteaether.github.io/2025/announcing-byteaetherweakevent-v100/",
      "title": "Announcing ByteAether.WeakEvent v1.0.0",
      "content_html": "<p>We are thrilled to announce the release of <a href=\"https://github.com/ByteAether/WeakEvent\" target=\"_blank\" rel=\"noopener noreferrer\"><strong>ByteAether.WeakEvent v1.0.0</strong></a>, a NuGet library designed to revolutionize event management in .NET. This release marks the culmination of months of development, testing, and community feedback. With a focus on memory safety and ease-of-use, ByteAether.WeakEvent empowers software engineers and architects to build decoupled, maintainable, and high-performance applications without the pitfalls of memory leaks caused by traditional event subscriptions.</p><h2 id=\"tackling-a-common-challenge-in-.net\" tabindex=\"-1\">Tackling a Common Challenge in .NET</h2><p>In modern .NET applications, event-driven programming is ubiquitous. Whether you are developing rich desktop experiences with WPF, dynamic web applications with Blazor, or any event-based system, managing event subscriptions efficiently is critical. A common challenge is that the standard event subscription model holds strong references to subscribers, often leading to unintended memory retention and, ultimately, memory leaks.</p><p>The <strong>weak event pattern</strong> offers a solution by holding references to event subscribers weakly. In this model, the garbage collector can reclaim subscriber memory even when they remain subscribed to an event, provided no other strong references exist. This release of ByteAether.WeakEvent brings this pattern to your fingertips with a clean API, automatic cleanup of defunct subscriptions, and a robust publish–subscribe mechanism tailored for .NET applications.</p><p>For more details on the concepts behind weak events and publish–subscribe patterns, refer to our in-depth article on the subject. You can also check out the <a href=\"https://github.com/ByteAether/WeakEvent\" target=\"_blank\" rel=\"noopener noreferrer\">GitHub repository</a> for source code and additional documentation.</p><h2 id=\"understanding-the-weak-event-pattern\" tabindex=\"-1\">Understanding the Weak Event Pattern</h2><h3 id=\"what-is-a-weak-event%3F\" tabindex=\"-1\">What Is a Weak Event?</h3><p>In conventional .NET event handling, the publisher holds a strong reference to each subscriber through its delegate list. If subscribers are not explicitly removed, these references persist, keeping objects in memory even after they are no longer needed. This is the primary cause of memory leaks in event-driven systems.</p><p>A <strong>weak event</strong> solves this by holding subscribers via weak references. A weak reference permits the garbage collector to reclaim an object's memory if no other strong references exist. This behavior is especially useful in scenarios where the publisher has a longer lifetime than its subscribers.</p><h3 id=\"the-theory-behind-weak-references\" tabindex=\"-1\">The Theory Behind Weak References</h3><p>As explained in foundational materials on weak event management:</p><ul><li><strong>Memory Efficiency:</strong> Weak references ensure that an object is not kept alive solely by its event subscription.</li><li><strong>Decoupled Design:</strong> Publishers and subscribers can evolve independently, fostering better modularity.</li><li><strong>Automatic Cleanup:</strong> There is no need to worry about manually unsubscribing every time a component is disposed. The weak reference mechanism handles cleanup, thereby reducing the risk of human error.</li></ul><p>Consider this simplified code example that demonstrates the essence of weak references in event handling:</p><pre class=\"language-csharp\"><code class=\"language-csharp\"><span class=\"token keyword\">public</span> <span class=\"token keyword\">class</span> <span class=\"token class-name\">Publisher</span>\n<span class=\"token punctuation\">{</span>\n\t<span class=\"token comment\">// List of weak references to event handlers</span>\n\t<span class=\"token keyword\">private</span> <span class=\"token class-name\">List<span class=\"token punctuation\">&lt;</span>WeakReference<span class=\"token punctuation\">&lt;</span>EventHandler<span class=\"token punctuation\">></span><span class=\"token punctuation\">></span></span> _handlers <span class=\"token operator\">=</span> <span class=\"token keyword\">new</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\n\t<span class=\"token keyword\">public</span> <span class=\"token return-type class-name\"><span class=\"token keyword\">void</span></span> <span class=\"token function\">Subscribe</span><span class=\"token punctuation\">(</span><span class=\"token class-name\">EventHandler</span> handler<span class=\"token punctuation\">)</span>\n\t<span class=\"token punctuation\">{</span>\n\t\t_handlers<span class=\"token punctuation\">.</span><span class=\"token function\">Add</span><span class=\"token punctuation\">(</span><span class=\"token keyword\">new</span> <span class=\"token constructor-invocation class-name\">WeakReference<span class=\"token punctuation\">&lt;</span>EventHandler<span class=\"token punctuation\">></span></span><span class=\"token punctuation\">(</span>handler<span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\t<span class=\"token punctuation\">}</span>\n\n\t<span class=\"token keyword\">public</span> <span class=\"token return-type class-name\"><span class=\"token keyword\">void</span></span> <span class=\"token function\">RaiseEvent</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span>\n\t<span class=\"token punctuation\">{</span>\n\t\t<span class=\"token keyword\">foreach</span> <span class=\"token punctuation\">(</span><span class=\"token class-name\"><span class=\"token keyword\">var</span></span> weakHandler <span class=\"token keyword\">in</span> _handlers<span class=\"token punctuation\">)</span>\n\t\t<span class=\"token punctuation\">{</span>\n\t\t\t<span class=\"token keyword\">if</span> <span class=\"token punctuation\">(</span>weakHandler<span class=\"token punctuation\">.</span><span class=\"token function\">TryGetTarget</span><span class=\"token punctuation\">(</span><span class=\"token keyword\">out</span> <span class=\"token class-name\">EventHandler</span> handler<span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span>\n\t\t\t<span class=\"token punctuation\">{</span>\n\t\t\t\thandler<span class=\"token punctuation\">.</span><span class=\"token function\">Invoke</span><span class=\"token punctuation\">(</span><span class=\"token keyword\">this</span><span class=\"token punctuation\">,</span> EventArgs<span class=\"token punctuation\">.</span>Empty<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\t\t\t<span class=\"token punctuation\">}</span>\n\t\t<span class=\"token punctuation\">}</span>\n\t<span class=\"token punctuation\">}</span>\n<span class=\"token punctuation\">}</span></code></pre><p>In this model, event subscribers are not held strongly, which allows the garbage collector to reclaim memory for any subscriber that is no longer in use.</p><h2 id=\"key-benefits-of-byteaether.weakevent\" tabindex=\"-1\">Key Benefits of ByteAether.WeakEvent</h2><h3 id=\"memory-efficiency-and-automatic-cleanup\" tabindex=\"-1\">Memory Efficiency and Automatic Cleanup</h3><p>One of the primary advantages of using ByteAether.WeakEvent is its ability to manage memory efficiently. By storing subscribers as weak references, the library ensures that objects are not unnecessarily held in memory. This is particularly beneficial for applications with long lifetimes, where small memory leaks can accumulate over time and degrade performance.</p><h3 id=\"simplified-event-management\" tabindex=\"-1\">Simplified Event Management</h3><p>Traditionally, developers must be vigilant in manually unsubscribing events—an often error-prone process. ByteAether.WeakEvent automates the cleanup process by removing dead references whenever an event is raised. This simplifies event management, reduces boilerplate code, and minimizes the risk of memory leaks.</p><h3 id=\"versatility-across-application-types\" tabindex=\"-1\">Versatility Across Application Types</h3><p>The library is designed to be flexible:</p><ul><li><strong>Events With or Without Data:</strong> Whether you need to send event data or simply signal that an event has occurred, ByteAether.WeakEvent provides a straightforward API for both scenarios.</li><li><strong>Support for Asynchronous Patterns:</strong> With native support for asynchronous publishing, the library is well-suited for modern application architectures that leverage async/await patterns.</li><li><strong>Ideal for UI Frameworks:</strong> In frameworks like Blazor, where component lifecycles can be complex, the library's weak reference management ensures that components are garbage collected promptly after disposal.</li></ul><h2 id=\"exploring-the-api-and-usage-examples\" tabindex=\"-1\">Exploring the API and Usage Examples</h2><h3 id=\"simple-event-subscription\" tabindex=\"-1\">Simple Event Subscription</h3><p>For events that do not require additional data, the API is as simple as subscribing a handler and publishing the event:</p><pre class=\"language-csharp\"><code class=\"language-csharp\"><span class=\"token keyword\">using</span> <span class=\"token namespace\">ByteAether<span class=\"token punctuation\">.</span>WeakEvent</span><span class=\"token punctuation\">;</span>\n\n<span class=\"token comment\">// Create an instance of the weak event without event data</span>\n<span class=\"token class-name\"><span class=\"token keyword\">var</span></span> myEvent <span class=\"token operator\">=</span> <span class=\"token keyword\">new</span> <span class=\"token constructor-invocation class-name\">WeakEvent</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\n<span class=\"token comment\">// Create a subscriber and subscribe</span>\n<span class=\"token class-name\"><span class=\"token keyword\">var</span></span> subscriber <span class=\"token operator\">=</span> <span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">=></span> Console<span class=\"token punctuation\">.</span><span class=\"token function\">WriteLine</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"Event received!\"</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\nmyEvent<span class=\"token punctuation\">.</span><span class=\"token function\">Subscribe</span><span class=\"token punctuation\">(</span>subscriber<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\n<span class=\"token comment\">// Raise the event</span>\n<span class=\"token keyword\">await</span> myEvent<span class=\"token punctuation\">.</span><span class=\"token function\">PublishAsync</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span></code></pre><p>In this example, the handler is invoked if its target is still alive. Dead subscribers are automatically pruned from the subscription list when the event is raised.</p><h3 id=\"event-subscription-with-data\" tabindex=\"-1\">Event Subscription with Data</h3><p>When you need to transmit data along with the event, the <code>WeakEvent&lt;TEvent&gt;</code> class comes into play:</p><pre class=\"language-csharp\"><code class=\"language-csharp\"><span class=\"token keyword\">using</span> <span class=\"token namespace\">ByteAether<span class=\"token punctuation\">.</span>WeakEvent</span><span class=\"token punctuation\">;</span>\n\n<span class=\"token comment\">// Create an instance of the weak event with event data</span>\n<span class=\"token class-name\"><span class=\"token keyword\">var</span></span> myEvent <span class=\"token operator\">=</span> <span class=\"token keyword\">new</span> <span class=\"token constructor-invocation class-name\">WeakEvent<span class=\"token punctuation\">&lt;</span>MyEventData<span class=\"token punctuation\">></span></span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\n<span class=\"token comment\">// Create a subscriber and subscribe</span>\n<span class=\"token class-name\"><span class=\"token keyword\">var</span></span> subscriber <span class=\"token operator\">=</span> <span class=\"token punctuation\">(</span><span class=\"token class-name\">MyEventData</span> data<span class=\"token punctuation\">)</span> <span class=\"token operator\">=></span> Console<span class=\"token punctuation\">.</span><span class=\"token function\">WriteLine</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"Received: \"</span> <span class=\"token operator\">+</span> data<span class=\"token punctuation\">.</span>Message<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\nmyEvent<span class=\"token punctuation\">.</span><span class=\"token function\">Subscribe</span><span class=\"token punctuation\">(</span>subscriber<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\n<span class=\"token comment\">// Raise the event with data</span>\n<span class=\"token keyword\">await</span> myEvent<span class=\"token punctuation\">.</span><span class=\"token function\">PublishAsync</span><span class=\"token punctuation\">(</span><span class=\"token keyword\">new</span> <span class=\"token constructor-invocation class-name\">MyEventData</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"Hello, World!\"</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\n<span class=\"token comment\">// Define your event data</span>\n<span class=\"token keyword\">public</span> <span class=\"token keyword\">record</span> <span class=\"token class-name\">MyEventData</span><span class=\"token punctuation\">(</span><span class=\"token class-name\"><span class=\"token keyword\">string</span></span> Message<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span></code></pre><p>This approach allows your events to be rich in context, with subscribers receiving any necessary data without compromising the integrity of the memory management system.</p><h2 id=\"how-byteaether.weakevent-works-under-the-hood\" tabindex=\"-1\">How ByteAether.WeakEvent Works Under the Hood</h2><h3 id=\"the-publish%E2%80%93subscribe-paradigm\" tabindex=\"-1\">The Publish–Subscribe Paradigm</h3><p>At its core, ByteAether.WeakEvent embraces the <strong>publish–subscribe</strong> model, a design pattern that decouples the event publisher from its subscribers. In this architecture:</p><ul><li><strong>Publishers</strong> emit events without any knowledge of which components are listening.</li><li><strong>Subscribers</strong> listen for events and react accordingly, without the need for explicit deregistration.</li></ul><p>This decoupling not only simplifies code but also enhances scalability and maintainability—attributes that are critical in both desktop and web applications.</p><h3 id=\"detailed-implementation-insights\" tabindex=\"-1\">Detailed Implementation Insights</h3><p>The library leverages the built-in .NET <a href=\"https://learn.microsoft.com/en-us/dotnet/standard/garbage-collection/weak-references\" target=\"_blank\" rel=\"noopener noreferrer\"><code>WeakReference&lt;T&gt;</code></a> to hold event subscribers. When an event is published:</p><ul><li>It iterates through the list of weak references.</li><li>Attempts to retrieve the target object for each subscriber.</li><li>Invokes the event handler only if the target is still alive.</li><li>Cleans up any references that have been collected by the garbage collector.</li></ul><p>This strategy ensures that your application's memory footprint remains minimal, as there is no lingering of obsolete event handlers. Detailed examples and explanations can be found in our <a href=\"https://github.com/ByteAether/WeakEvent\" target=\"_blank\" rel=\"noopener noreferrer\">documentation</a> and our in-depth article on the weak event pattern.</p><h2 id=\"real-world-applications-and-scenarios\" tabindex=\"-1\">Real-World Applications and Scenarios</h2><h3 id=\"ui-development-with-blazor-and-wpf\" tabindex=\"-1\">UI Development with Blazor and WPF</h3><p>In UI frameworks such as Blazor and WPF, the lifecycle management of components is crucial. Consider a Blazor component that subscribes to events during its initialization:</p><pre class=\"language-razor\"><code class=\"language-razor\"><span class=\"token block\"><span class=\"token keyword\">@code</span> <span class=\"token csharp language-csharp\"><span class=\"token punctuation\">{</span>\n\t<span class=\"token punctuation\">[</span><span class=\"token attribute\"><span class=\"token class-name\">Inject</span></span><span class=\"token punctuation\">]</span>\n\t<span class=\"token keyword\">protected</span> <span class=\"token keyword\">readonly</span> <span class=\"token return-type class-name\">Publisher</span> _publisher <span class=\"token punctuation\">{</span> <span class=\"token keyword\">get</span><span class=\"token punctuation\">;</span> <span class=\"token keyword\">set</span><span class=\"token punctuation\">;</span> <span class=\"token punctuation\">}</span> <span class=\"token operator\">=</span> <span class=\"token keyword\">default</span><span class=\"token operator\">!</span><span class=\"token punctuation\">;</span>\n\n\t<span class=\"token keyword\">protected</span> <span class=\"token keyword\">override</span> <span class=\"token return-type class-name\"><span class=\"token keyword\">void</span></span> <span class=\"token function\">OnInitialized</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span>\n\t<span class=\"token punctuation\">{</span>\n\t\t_publisher<span class=\"token punctuation\">.</span>OnPublish<span class=\"token punctuation\">.</span><span class=\"token function\">Subscribe</span><span class=\"token punctuation\">(</span>OnEvent<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\t<span class=\"token punctuation\">}</span>\n\n\t<span class=\"token keyword\">public</span> <span class=\"token return-type class-name\"><span class=\"token keyword\">void</span></span> <span class=\"token function\">OnEvent</span><span class=\"token punctuation\">(</span><span class=\"token class-name\">MyEventData</span> eventData<span class=\"token punctuation\">)</span>\n\t<span class=\"token punctuation\">{</span>\n\t\tConsole<span class=\"token punctuation\">.</span><span class=\"token function\">WriteLine</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"Event received in Blazor component.\"</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\t<span class=\"token punctuation\">}</span>\n\n\t<span class=\"token keyword\">public</span> <span class=\"token return-type class-name\"><span class=\"token keyword\">void</span></span> <span class=\"token function\">Dispose</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span>\n\t<span class=\"token punctuation\">{</span>\n\t\t<span class=\"token comment\">// No need to manually unsubscribe, the weak reference handles cleanup.</span>\n\t<span class=\"token punctuation\">}</span>\n<span class=\"token punctuation\">}</span></span></span></code></pre><p>In this scenario, even if a component is disposed, its subscription does not prevent garbage collection. This is a critical advantage in long-running applications where manual unsubscription could be easily overlooked, leading to potential memory leaks.</p><h3 id=\"server-side-and-distributed-systems\" tabindex=\"-1\">Server-Side and Distributed Systems</h3><p>For backend services and distributed systems, resource management is paramount. ByteAether.WeakEvent's ability to automatically manage event subscriptions means that your server-side applications can scale more reliably. When objects are no longer in use, they are automatically cleaned up, reducing the overhead associated with manual memory management and ensuring smoother performance.</p><h3 id=\"cross-platform-benefits\" tabindex=\"-1\">Cross-Platform Benefits</h3><p>While our focus is on .NET, the principles behind weak event management apply broadly across programming environments. Whether you are working with JavaScript's reactive libraries, Java's weak references, or even observer patterns in C++, the core concept remains the same: decoupling event publishers from subscribers results in more resilient and maintainable systems.</p><h2 id=\"getting-started-with-byteaether.weakevent\" tabindex=\"-1\">Getting Started with ByteAether.WeakEvent</h2><h3 id=\"installation-made-easy\" tabindex=\"-1\">Installation Made Easy</h3><p>The ByteAether.WeakEvent library is available as a <a href=\"https://www.nuget.org/packages/ByteAether.WeakEvent/\" target=\"_blank\" rel=\"noopener noreferrer\">NuGet package</a>. To install, simply run:</p><pre class=\"language-sh\"><code class=\"language-sh\">dotnet <span class=\"token function\">add</span> package ByteAether.WeakEvent</code></pre><p>For those who wish to experiment with the latest features, you can also specify a preview version using the <code>--version</code> option.</p><h3 id=\"documentation-and-community\" tabindex=\"-1\">Documentation and Community</h3><p>For comprehensive documentation, API details, and additional usage examples, please visit the <a href=\"https://github.com/ByteAether/WeakEvent\" target=\"_blank\" rel=\"noopener noreferrer\">GitHub repository</a>. We encourage contributions and feedback from the community. If you encounter any issues or have suggestions, feel free to open an issue or submit a pull request.</p><h2 id=\"a-new-era-of-event-management-in-.net\" tabindex=\"-1\">A New Era of Event Management in .NET</h2><p>ByteAether.WeakEvent v1.0.0 sets a new standard for event handling in .NET. By embracing the weak event pattern, the library not only addresses common pitfalls like memory leaks but also simplifies your codebase, allowing you to focus on building great features. Whether you are developing desktop applications, dynamic web apps, or large-scale distributed systems, this library is designed to make your event-driven programming more efficient, robust, and maintainable.</p><p>We invite software engineers and architects to integrate ByteAether.WeakEvent into their projects and experience the benefits of a lean, memory-safe event management system. Explore the <a href=\"https://github.com/ByteAether/WeakEvent\" target=\"_blank\" rel=\"noopener noreferrer\">GitHub repository</a> and get started today!</p><p>Happy coding!</p>",
      "date_published": "2025-03-18T00:00:00Z"
    }
    ,
    {
      "id": "https://byteaether.github.io/2025/hyperaction-api-a-reimagined-approach-to-http-api-design/",
      "url": "https://byteaether.github.io/2025/hyperaction-api-a-reimagined-approach-to-http-api-design/",
      "title": "HyperAction API: A Reimagined Approach to HTTP API Design",
      "content_html": "<p>Traditional <a href=\"https://ics.uci.edu/~fielding/pubs/dissertation/rest_arch_style.htm\" target=\"_blank\" rel=\"noopener noreferrer\">REST</a> designs emphasize <a href=\"https://en.wikipedia.org/wiki/Create,_read,_update_and_delete\" target=\"_blank\" rel=\"noopener noreferrer\">CRUD</a> operations over distinct business actions, forcing developers to implement significant business logic on the client side while backends simply translate raw database records into HTTP responses. As a result, the intended business rules become obscured and must be redundantly re-implemented on the server side—complicating maintenance and creating security vulnerabilities.</p><p>One major flaw arises from shared data models that are designed around generic resources rather than tailored to specific business features. For example, a single &quot;User&quot; endpoint might return all associated details—including sensitive fields like email addresses—even when the client requires only a list of online usernames. This forces clients to make multiple API calls to compile the necessary data and increases the risk of unintended data exposure.</p><p>In contrast, adopting a <a href=\"https://www.jimmybogard.com/vertical-slice-architecture/\" target=\"_blank\" rel=\"noopener noreferrer\">vertical slice architecture</a>—where each feature or business action is developed as a self-contained unit (encapsulating all layers from user interface to data access)—directly addresses these issues. Vertical slices isolate business logic, ensuring that changes in one domain do not affect unrelated parts of the system. This approach simplifies development and maintenance while aligning API behavior with specific business capabilities.</p><p>Drawing on extensive experience with both large-scale and smaller applications, the HyperAction API convention reimagines HTTP API design by centering endpoints around clear, action-oriented business operations. This method enhances security, minimizes redundancy, and fosters a more maintainable system architecture.</p><hr/><p>This document outlines the technical guidelines for the new <strong>HyperAction API</strong> convention. Departing from traditional REST paradigms, HyperAction API enforces batch processing at every endpoint, aligns endpoints with specific business actions, and incorporates explicit mechanisms for consistency and asynchronous operations. The objective is to create a clear and efficient contract between clients and servers that minimizes the common pitfalls of REST while meeting modern application needs.</p><p>Each design decision is explained by comparing conventional REST approaches with their challenges, then detailing our design decisions with updated examples that adhere to the batching principle.</p><h2 id=\"key-concepts\" tabindex=\"-1\">Key Concepts</h2><ul><li><p><strong>Vertical Slice Architecture:</strong> A design approach where each business feature is developed as a self-contained unit, encapsulating all necessary layers (from user interface to data access). This minimizes interdependencies and simplifies maintenance.</p></li><li><p><strong>Action-Oriented Endpoints:</strong> Endpoints designed to execute specific business operations rather than generic CRUD functions, ensuring that business logic is directly integrated into the API.</p></li><li><p><strong>Batch Processing:</strong> The practice of handling multiple data items in a single API call, which promotes atomic operations and reduces the need for multiple round-trips between the client and server.</p></li></ul><h2 id=\"1.-use-singular-naming-conventions\" tabindex=\"-1\">1. Use Singular Naming Conventions</h2><h3 id=\"traditional-rest-approach\" tabindex=\"-1\">Traditional REST Approach</h3><p>Many REST APIs use plural names (e.g., <code>/users</code>, <code>/orders</code>) to indicate collections of resources. This approach can be ambiguous when handling irregular plurals.</p><h3 id=\"challenges\" tabindex=\"-1\">Challenges</h3><p>Plural names may lead to confusion with irregular words—such as &quot;mouse&quot; versus &quot;mice&quot; or &quot;octopus&quot; versus &quot;octopi&quot;—and can complicate the mental model of the API. In a batch-based system where every endpoint already handles multiple items, the plurality is implied by the data structure, not the endpoint name.</p><h3 id=\"hyperaction-api-decision\" tabindex=\"-1\">HyperAction API Decision</h3><p>In HyperAction API, all endpoint and model names are singular. Even though requests and responses always use arrays, the endpoint name itself represents the individual item type. This convention avoids ambiguity, simplifies naming, and reinforces that the batch behavior is a global characteristic of the API.</p><h4 id=\"example\" tabindex=\"-1\">Example</h4><p>An endpoint to retrieve users is named singularly:</p><p><strong>HTTP Request</strong></p><pre class=\"language-http\"><code class=\"language-http\"><span class=\"token request-line\"><span class=\"token method property\">GET</span> <span class=\"token request-target url\">/user?limit=10&amp;offset=0</span> <span class=\"token http-version property\">HTTP/1.1</span></span>\n<span class=\"token header\"><span class=\"token header-name keyword\">Host</span><span class=\"token punctuation\">:</span> <span class=\"token header-value\">api.example.com</span></span>\n<span class=\"token header\"><span class=\"token header-name keyword\">Accept</span><span class=\"token punctuation\">:</span> <span class=\"token header-value\">application/json</span></span></code></pre><p><strong>HTTP Response</strong></p><pre class=\"language-http\"><code class=\"language-http\"><span class=\"token response-status\"><span class=\"token http-version property\">HTTP/1.1</span> <span class=\"token status-code number\">200</span> <span class=\"token reason-phrase string\">OK</span></span>\n<span class=\"token header\"><span class=\"token header-name keyword\">Content-Type</span><span class=\"token punctuation\">:</span> <span class=\"token header-value\">application/json</span></span>\n<span class=\"token header\"><span class=\"token header-name keyword\">X-Count</span><span class=\"token punctuation\">:</span> <span class=\"token header-value\">10</span></span>\n<span class=\"token header\"><span class=\"token header-name keyword\">X-Limit</span><span class=\"token punctuation\">:</span> <span class=\"token header-value\">10</span></span>\n<span class=\"token header\"><span class=\"token header-name keyword\">X-Offset</span><span class=\"token punctuation\">:</span> <span class=\"token header-value\">0</span></span>\n<span class=\"token application-json\">\n<span class=\"token punctuation\">[</span>\n\t<span class=\"token punctuation\">{</span> <span class=\"token string-property property\">\"id\"</span><span class=\"token operator\">:</span> <span class=\"token number\">1</span><span class=\"token punctuation\">,</span> <span class=\"token string-property property\">\"name\"</span><span class=\"token operator\">:</span> <span class=\"token string\">\"Alice\"</span> <span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span>\n\t<span class=\"token punctuation\">{</span> <span class=\"token string-property property\">\"id\"</span><span class=\"token operator\">:</span> <span class=\"token number\">2</span><span class=\"token punctuation\">,</span> <span class=\"token string-property property\">\"name\"</span><span class=\"token operator\">:</span> <span class=\"token string\">\"Bob\"</span> <span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span>\n\t<span class=\"token operator\">...</span>\n<span class=\"token punctuation\">]</span>\n</span></code></pre><p>Similarly, a business action such as account verification remains singular:</p><p><strong>HTTP Request</strong></p><pre class=\"language-http\"><code class=\"language-http\"><span class=\"token request-line\"><span class=\"token method property\">POST</span> <span class=\"token request-target url\">/account/verify</span> <span class=\"token http-version property\">HTTP/1.1</span></span>\n<span class=\"token header\"><span class=\"token header-name keyword\">Host</span><span class=\"token punctuation\">:</span> <span class=\"token header-value\">api.example.com</span></span>\n<span class=\"token header\"><span class=\"token header-name keyword\">Content-Type</span><span class=\"token punctuation\">:</span> <span class=\"token header-value\">application/json</span></span>\n<span class=\"token application-json\">\n<span class=\"token punctuation\">[</span>\n\t<span class=\"token punctuation\">{</span> <span class=\"token string-property property\">\"userId\"</span><span class=\"token operator\">:</span> <span class=\"token number\">789</span><span class=\"token punctuation\">,</span> <span class=\"token string-property property\">\"token\"</span><span class=\"token operator\">:</span> <span class=\"token string\">\"ABC123XYZ\"</span> <span class=\"token punctuation\">}</span>\n<span class=\"token punctuation\">]</span>\n</span></code></pre><p><strong>HTTP Response</strong></p><pre class=\"language-http\"><code class=\"language-http\"><span class=\"token response-status\"><span class=\"token http-version property\">HTTP/1.1</span> <span class=\"token status-code number\">207</span> <span class=\"token reason-phrase string\">Multi-Status</span></span>\n<span class=\"token header\"><span class=\"token header-name keyword\">Content-Type</span><span class=\"token punctuation\">:</span> <span class=\"token header-value\">application/json</span></span>\n<span class=\"token application-json\">\n<span class=\"token punctuation\">[</span>\n\t<span class=\"token punctuation\">{</span> <span class=\"token string-property property\">\"userId\"</span><span class=\"token operator\">:</span> <span class=\"token number\">789</span><span class=\"token punctuation\">,</span> <span class=\"token string-property property\">\"status\"</span><span class=\"token operator\">:</span> <span class=\"token number\">200</span> <span class=\"token punctuation\">}</span>\n<span class=\"token punctuation\">]</span>\n</span></code></pre><p>This naming convention clarifies that although each payload contains multiple items, the fundamental unit of operation remains singular.</p><h2 id=\"2.-headers-instead-of-envelope-models\" tabindex=\"-1\">2. Headers Instead of Envelope Models</h2><h3 id=\"traditional-rest-approach-1\" tabindex=\"-1\">Traditional REST Approach</h3><p>Many APIs wrap data in envelope objects that include metadata like status and pagination details, leading to additional nesting and complexity.</p><h3 id=\"challenges-1\" tabindex=\"-1\">Challenges</h3><p>The extra layer of an envelope model forces clients to parse both the data and metadata, which complicates the data extraction and processing logic.</p><h3 id=\"hyperaction-api-decision-1\" tabindex=\"-1\">HyperAction API Decision</h3><p>Responses for list endpoints return a raw array of items. Metadata such as total count, offset, and limit is communicated exclusively through HTTP headers like <code>X-Count</code>, <code>X-Total-Count</code>, <code>X-Limit</code>, and <code>X-Offset</code>.</p><h4 id=\"example-1\" tabindex=\"-1\">Example</h4><p>A product listing request:</p><p><strong>HTTP Request</strong></p><pre class=\"language-http\"><code class=\"language-http\"><span class=\"token request-line\"><span class=\"token method property\">GET</span> <span class=\"token request-target url\">/product?limit=20&amp;offset=0</span> <span class=\"token http-version property\">HTTP/1.1</span></span>\n<span class=\"token header\"><span class=\"token header-name keyword\">Host</span><span class=\"token punctuation\">:</span> <span class=\"token header-value\">api.example.com</span></span>\n<span class=\"token header\"><span class=\"token header-name keyword\">Accept</span><span class=\"token punctuation\">:</span> <span class=\"token header-value\">application/json</span></span></code></pre><p><strong>HTTP Response</strong></p><pre class=\"language-http\"><code class=\"language-http\"><span class=\"token response-status\"><span class=\"token http-version property\">HTTP/1.1</span> <span class=\"token status-code number\">200</span> <span class=\"token reason-phrase string\">OK</span></span>\n<span class=\"token header\"><span class=\"token header-name keyword\">Content-Type</span><span class=\"token punctuation\">:</span> <span class=\"token header-value\">application/json</span></span>\n<span class=\"token header\"><span class=\"token header-name keyword\">X-Count</span><span class=\"token punctuation\">:</span> <span class=\"token header-value\">20</span></span>\n<span class=\"token header\"><span class=\"token header-name keyword\">X-Total-Count</span><span class=\"token punctuation\">:</span> <span class=\"token header-value\">250</span></span>\n<span class=\"token header\"><span class=\"token header-name keyword\">X-Limit</span><span class=\"token punctuation\">:</span> <span class=\"token header-value\">20</span></span>\n<span class=\"token header\"><span class=\"token header-name keyword\">X-Offset</span><span class=\"token punctuation\">:</span> <span class=\"token header-value\">0</span></span>\n<span class=\"token application-json\">\n<span class=\"token punctuation\">[</span>\n\t<span class=\"token punctuation\">{</span> <span class=\"token string-property property\">\"productId\"</span><span class=\"token operator\">:</span> <span class=\"token number\">101</span><span class=\"token punctuation\">,</span> <span class=\"token string-property property\">\"name\"</span><span class=\"token operator\">:</span> <span class=\"token string\">\"Widget A\"</span><span class=\"token punctuation\">,</span> <span class=\"token string-property property\">\"price\"</span><span class=\"token operator\">:</span> <span class=\"token number\">9.99</span> <span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span>\n\t<span class=\"token punctuation\">{</span> <span class=\"token string-property property\">\"productId\"</span><span class=\"token operator\">:</span> <span class=\"token number\">102</span><span class=\"token punctuation\">,</span> <span class=\"token string-property property\">\"name\"</span><span class=\"token operator\">:</span> <span class=\"token string\">\"Widget B\"</span><span class=\"token punctuation\">,</span> <span class=\"token string-property property\">\"price\"</span><span class=\"token operator\">:</span> <span class=\"token number\">12.99</span> <span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span>\n\t<span class=\"token operator\">...</span>\n<span class=\"token punctuation\">]</span>\n</span></code></pre><p>Based on <a href=\"https://datatracker.ietf.org/doc/html/rfc6648\" target=\"_blank\" rel=\"noopener noreferrer\">RFC 6648</a>, the &quot;X-&quot; prefix on custom headers is not a requirement. This document does not enforce the use of the &quot;X-&quot; prefix; however, the examples include it because this naming convention is widely recognized and helps make custom headers easier to read and understand.</p><h2 id=\"3.-batch-endpoints-for-all-operations\" tabindex=\"-1\">3. Batch Endpoints for All Operations</h2><h3 id=\"traditional-rest-approach-2\" tabindex=\"-1\">Traditional REST Approach</h3><p>RESTful designs typically offer endpoints such as <code>GET /items/{id}</code> or <code>PUT /items/{id}</code> that focus on individual resources. When multiple items must be handled, the client is forced to make several calls or use loosely defined batch endpoints.</p><h3 id=\"challenges-2\" tabindex=\"-1\">Challenges</h3><p>Focusing on single resources increases round-trips and may lead to inconsistency when operations naturally involve multiple entities. It also forces clients to stitch together logic for batch processing, which can become cumbersome.</p><h3 id=\"hyperaction-api-decision-2\" tabindex=\"-1\">HyperAction API Decision</h3><p>Every endpoint in HyperAction API is a batch endpoint. There is no dedicated endpoint for a single-item operation; instead, all operations—whether for retrieval, creation, or update—are designed to work with arrays. This not only ensures atomic, batch-based processing but also enforces a mindset where the client always works with collections of data.</p><h4 id=\"example-2\" tabindex=\"-1\">Example</h4><p>To retrieve items, a client issues:</p><p><strong>HTTP Request</strong></p><pre class=\"language-http\"><code class=\"language-http\"><span class=\"token request-line\"><span class=\"token method property\">GET</span> <span class=\"token request-target url\">/item?filter=id:123,456</span> <span class=\"token http-version property\">HTTP/1.1</span></span>\n<span class=\"token header\"><span class=\"token header-name keyword\">Host</span><span class=\"token punctuation\">:</span> <span class=\"token header-value\">api.example.com</span></span>\n<span class=\"token header\"><span class=\"token header-name keyword\">Accept</span><span class=\"token punctuation\">:</span> <span class=\"token header-value\">application/json</span></span></code></pre><p><strong>HTTP Response</strong></p><pre class=\"language-http\"><code class=\"language-http\"><span class=\"token response-status\"><span class=\"token http-version property\">HTTP/1.1</span> <span class=\"token status-code number\">200</span> <span class=\"token reason-phrase string\">OK</span></span>\n<span class=\"token header\"><span class=\"token header-name keyword\">Content-Type</span><span class=\"token punctuation\">:</span> <span class=\"token header-value\">application/json</span></span>\n<span class=\"token header\"><span class=\"token header-name keyword\">X-Count</span><span class=\"token punctuation\">:</span> <span class=\"token header-value\">2</span></span>\n<span class=\"token header\"><span class=\"token header-name keyword\">X-Total-Count</span><span class=\"token punctuation\">:</span> <span class=\"token header-value\">2</span></span>\n<span class=\"token header\"><span class=\"token header-name keyword\">X-Limit</span><span class=\"token punctuation\">:</span> <span class=\"token header-value\">50</span></span>\n<span class=\"token header\"><span class=\"token header-name keyword\">X-Offset</span><span class=\"token punctuation\">:</span> <span class=\"token header-value\">0</span></span>\n<span class=\"token application-json\">\n<span class=\"token punctuation\">[</span>\n\t<span class=\"token punctuation\">{</span> <span class=\"token string-property property\">\"id\"</span><span class=\"token operator\">:</span> <span class=\"token number\">123</span><span class=\"token punctuation\">,</span> <span class=\"token string-property property\">\"name\"</span><span class=\"token operator\">:</span> <span class=\"token string\">\"Item One\"</span><span class=\"token punctuation\">,</span> <span class=\"token string-property property\">\"price\"</span><span class=\"token operator\">:</span> <span class=\"token number\">14.99</span> <span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span>\n\t<span class=\"token punctuation\">{</span> <span class=\"token string-property property\">\"id\"</span><span class=\"token operator\">:</span> <span class=\"token number\">456</span><span class=\"token punctuation\">,</span> <span class=\"token string-property property\">\"name\"</span><span class=\"token operator\">:</span> <span class=\"token string\">\"Item Two\"</span><span class=\"token punctuation\">,</span> <span class=\"token string-property property\">\"price\"</span><span class=\"token operator\">:</span> <span class=\"token number\">7.99</span> <span class=\"token punctuation\">}</span>\n<span class=\"token punctuation\">]</span>\n</span></code></pre><h2 id=\"4.-pagination-using-limit-and-offset\" tabindex=\"-1\">4. Pagination Using Limit and Offset</h2><h3 id=\"traditional-rest-approach-3\" tabindex=\"-1\">Traditional REST Approach</h3><p>Many REST APIs use <code>page</code> and <code>pageSize</code> parameters, which hide the actual dataset position and force clients to convert page numbers into offsets.</p><h3 id=\"challenges-3\" tabindex=\"-1\">Challenges</h3><p>The abstract concept of a page makes it harder to implement progressive loading or infinite scrolling. Clients must perform additional calculations to determine the proper slice of data.</p><h3 id=\"hyperaction-api-decision-3\" tabindex=\"-1\">HyperAction API Decision</h3><p>Endpoints return list results using explicit <code>limit</code> and <code>offset</code> parameters. This clear slicing method allows clients to directly control the amount of data and its position, simplifying custom pagination logic.</p><h4 id=\"example-3\" tabindex=\"-1\">Example</h4><p>A request for a subset of orders:</p><p><strong>HTTP Request</strong></p><pre class=\"language-http\"><code class=\"language-http\"><span class=\"token request-line\"><span class=\"token method property\">GET</span> <span class=\"token request-target url\">/order?limit=50&amp;offset=100</span> <span class=\"token http-version property\">HTTP/1.1</span></span>\n<span class=\"token header\"><span class=\"token header-name keyword\">Host</span><span class=\"token punctuation\">:</span> <span class=\"token header-value\">api.example.com</span></span>\n<span class=\"token header\"><span class=\"token header-name keyword\">Accept</span><span class=\"token punctuation\">:</span> <span class=\"token header-value\">application/json</span></span></code></pre><p><strong>HTTP Response</strong></p><pre class=\"language-http\"><code class=\"language-http\"><span class=\"token response-status\"><span class=\"token http-version property\">HTTP/1.1</span> <span class=\"token status-code number\">200</span> <span class=\"token reason-phrase string\">OK</span></span>\n<span class=\"token header\"><span class=\"token header-name keyword\">Content-Type</span><span class=\"token punctuation\">:</span> <span class=\"token header-value\">application/json</span></span>\n<span class=\"token header\"><span class=\"token header-name keyword\">X-Count</span><span class=\"token punctuation\">:</span> <span class=\"token header-value\">50</span></span>\n<span class=\"token header\"><span class=\"token header-name keyword\">X-Total-Count</span><span class=\"token punctuation\">:</span> <span class=\"token header-value\">1000</span></span>\n<span class=\"token header\"><span class=\"token header-name keyword\">X-Limit</span><span class=\"token punctuation\">:</span> <span class=\"token header-value\">50</span></span>\n<span class=\"token header\"><span class=\"token header-name keyword\">X-Offset</span><span class=\"token punctuation\">:</span> <span class=\"token header-value\">100</span></span>\n<span class=\"token application-json\">\n<span class=\"token punctuation\">[</span>\n\t<span class=\"token punctuation\">{</span> <span class=\"token string-property property\">\"orderId\"</span><span class=\"token operator\">:</span> <span class=\"token number\">201</span><span class=\"token punctuation\">,</span> <span class=\"token string-property property\">\"amount\"</span><span class=\"token operator\">:</span> <span class=\"token number\">99.99</span><span class=\"token punctuation\">,</span> <span class=\"token string-property property\">\"status\"</span><span class=\"token operator\">:</span> <span class=\"token string\">\"completed\"</span> <span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span>\n\t<span class=\"token punctuation\">{</span> <span class=\"token string-property property\">\"orderId\"</span><span class=\"token operator\">:</span> <span class=\"token number\">202</span><span class=\"token punctuation\">,</span> <span class=\"token string-property property\">\"amount\"</span><span class=\"token operator\">:</span> <span class=\"token number\">149.99</span><span class=\"token punctuation\">,</span> <span class=\"token string-property property\">\"status\"</span><span class=\"token operator\">:</span> <span class=\"token string\">\"pending\"</span> <span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span>\n\t<span class=\"token operator\">...</span>\n<span class=\"token punctuation\">]</span>\n</span></code></pre><p>With efficient pagination established, we now turn to designing endpoints that reflect discrete business actions—ensuring that each endpoint aligns with a specific operational capability.</p><h2 id=\"5.-business-logic%E2%80%93specific-endpoints\" tabindex=\"-1\">5. Business Logic–Specific Endpoints</h2><h3 id=\"traditional-rest-approach-4\" tabindex=\"-1\">Traditional REST Approach</h3><p>Generic endpoints such as <code>/users</code> or <code>/orders</code> attempt to cover all CRUD operations and often reuse generic models, which may lead to oversharing of data or the mixing of unrelated business logic.</p><h3 id=\"challenges-4\" tabindex=\"-1\">Challenges</h3><p>Using a single resource endpoint for various actions increases the risk of exposing unnecessary details and complicates segregation of business processes.</p><h3 id=\"hyperaction-api-decision-4\" tabindex=\"-1\">HyperAction API Decision</h3><p>Each endpoint is crafted for a specific business action. Every business-logic action endpoint now receives and responds with arrays of items, ensuring that the interface naturally supports batch processing.</p><h4 id=\"example-4\" tabindex=\"-1\">Example</h4><p>For account verification, instead of a single-object payload, the endpoint handles an array of verification requests:</p><p><strong>HTTP Request</strong></p><pre class=\"language-http\"><code class=\"language-http\"><span class=\"token request-line\"><span class=\"token method property\">POST</span> <span class=\"token request-target url\">/account/verify</span> <span class=\"token http-version property\">HTTP/1.1</span></span>\n<span class=\"token header\"><span class=\"token header-name keyword\">Host</span><span class=\"token punctuation\">:</span> <span class=\"token header-value\">api.example.com</span></span>\n<span class=\"token header\"><span class=\"token header-name keyword\">Content-Type</span><span class=\"token punctuation\">:</span> <span class=\"token header-value\">application/json</span></span>\n<span class=\"token application-json\">\n<span class=\"token punctuation\">[</span>\n\t<span class=\"token punctuation\">{</span> <span class=\"token string-property property\">\"userId\"</span><span class=\"token operator\">:</span> <span class=\"token number\">789</span><span class=\"token punctuation\">,</span> <span class=\"token string-property property\">\"token\"</span><span class=\"token operator\">:</span> <span class=\"token string\">\"ABC123XYZ\"</span> <span class=\"token punctuation\">}</span>\n<span class=\"token punctuation\">]</span>\n</span></code></pre><p><strong>HTTP Response</strong></p><pre class=\"language-http\"><code class=\"language-http\"><span class=\"token response-status\"><span class=\"token http-version property\">HTTP/1.1</span> <span class=\"token status-code number\">207</span> <span class=\"token reason-phrase string\">Multi-Status</span></span>\n<span class=\"token header\"><span class=\"token header-name keyword\">Content-Type</span><span class=\"token punctuation\">:</span> <span class=\"token header-value\">application/json</span></span>\n<span class=\"token application-json\">\n<span class=\"token punctuation\">[</span>\n\t<span class=\"token punctuation\">{</span> <span class=\"token string-property property\">\"userId\"</span><span class=\"token operator\">:</span> <span class=\"token number\">789</span><span class=\"token punctuation\">,</span> <span class=\"token string-property property\">\"status\"</span><span class=\"token operator\">:</span> <span class=\"token number\">200</span> <span class=\"token punctuation\">}</span>\n<span class=\"token punctuation\">]</span>\n</span></code></pre><p>This design keeps the interface consistent with batch processing while encapsulating business logic within dedicated endpoints. By designing endpoints specifically for discrete business actions, we sidestep the pitfalls of generic data handling and establish a clear, action-oriented approach to API design.</p><h2 id=\"6.-action-oriented-endpoints-over-crud\" tabindex=\"-1\">6. Action-Oriented Endpoints over CRUD</h2><h3 id=\"traditional-rest-approach-5\" tabindex=\"-1\">Traditional REST Approach</h3><p>Traditional CRUD endpoints focus solely on direct data manipulation and tend to oversimplify underlying business processes. This design forces clients to manage critical business logic—often duplicating effort and increasing security risks—while the backend merely acts as a conduit for raw data.</p><h3 id=\"challenges-5\" tabindex=\"-1\">Challenges</h3><p>Generic CRUD endpoints can force clients to manage business rules or risk unwanted side effects by treating operations as mere data transformations.</p><h3 id=\"hyperaction-api-decision-5\" tabindex=\"-1\">HyperAction API Decision</h3><p>Endpoints are explicitly designed for business actions. Even when these endpoints appear similar, they are modeled with distinct request and response payloads—and both are arrays. This makes the intended action explicit and separates it from simple data manipulation.</p><h4 id=\"example-5\" tabindex=\"-1\">Example</h4><p>For an address change, the API now expects an array of address change requests:</p><p><strong>HTTP Request</strong></p><pre class=\"language-http\"><code class=\"language-http\"><span class=\"token request-line\"><span class=\"token method property\">POST</span> <span class=\"token request-target url\">/account/change-address</span> <span class=\"token http-version property\">HTTP/1.1</span></span>\n<span class=\"token header\"><span class=\"token header-name keyword\">Host</span><span class=\"token punctuation\">:</span> <span class=\"token header-value\">api.example.com</span></span>\n<span class=\"token header\"><span class=\"token header-name keyword\">Content-Type</span><span class=\"token punctuation\">:</span> <span class=\"token header-value\">application/json</span></span>\n<span class=\"token application-json\">\n<span class=\"token punctuation\">[</span>\n\t<span class=\"token punctuation\">{</span>\n\t\t<span class=\"token string-property property\">\"userId\"</span><span class=\"token operator\">:</span> <span class=\"token number\">789</span><span class=\"token punctuation\">,</span>\n\t\t<span class=\"token string-property property\">\"address\"</span><span class=\"token operator\">:</span> <span class=\"token string\">\"123 New Street, Cityville, Country\"</span>\n\t<span class=\"token punctuation\">}</span>\n<span class=\"token punctuation\">]</span>\n</span></code></pre><p><strong>HTTP Response</strong></p><pre class=\"language-http\"><code class=\"language-http\"><span class=\"token response-status\"><span class=\"token http-version property\">HTTP/1.1</span> <span class=\"token status-code number\">207</span> <span class=\"token reason-phrase string\">Multi-Status</span></span>\n<span class=\"token header\"><span class=\"token header-name keyword\">Content-Type</span><span class=\"token punctuation\">:</span> <span class=\"token header-value\">application/json</span></span>\n<span class=\"token application-json\">\n<span class=\"token punctuation\">[</span>\n\t<span class=\"token punctuation\">{</span> <span class=\"token string-property property\">\"userId\"</span><span class=\"token operator\">:</span> <span class=\"token number\">789</span><span class=\"token punctuation\">,</span> <span class=\"token string-property property\">\"status\"</span><span class=\"token operator\">:</span> <span class=\"token number\">200</span> <span class=\"token punctuation\">}</span>\n<span class=\"token punctuation\">]</span>\n</span></code></pre><p>This explicit batching reinforces the notion that operations are designed for multiple items at once.</p><p>The HyperAction API employs action-oriented endpoints that integrate business logic directly into each operation. By embedding the intended business actions within the API itself, these endpoints offer clearer semantics and enhanced security. This shift not only streamlines client interactions by reducing the need for multiple calls but also ensures that each endpoint directly reflects a distinct business capability.</p><h2 id=\"7.-crud-like-operations-via-form-loading-and-saving\" tabindex=\"-1\">7. CRUD-Like Operations via Form Loading and Saving</h2><h3 id=\"traditional-rest-approach-6\" tabindex=\"-1\">Traditional REST Approach</h3><p>Typically, REST APIs implement simple CRUD endpoints for basic data manipulation without substantial business logic. This often results in &quot;proxy&quot; endpoints that merely transfer data from the client to the database without additional contextual processing.</p><p>The approach described here is most applicable to systems where the frontend and backend are developed together as a single, cohesive unit. In tightly integrated environments, close coordination between UI requirements and backend implementations can be achieved, making form loading and saving an effective pattern for streamlining data handling and reducing the need for redundant API calls. Conversely, for general public APIs—where the consumer or client code is developed by external parties—a more standardized and decoupled approach using traditional CRUD endpoints may be preferable to accommodate the varied requirements and integration patterns of third-party developers.</p><h3 id=\"challenges-6\" tabindex=\"-1\">Challenges</h3><ul><li><strong>Data Pre-Filling:</strong> When editing an entry, the form should be pre-populated with existing data and/or default values, a task that generic CRUD endpoints struggle to support.</li><li><strong>UI Requirements:</strong> For example, drop-down fields might only store an ID in the data payload, while the UI requires a human-readable label for display.</li><li><strong>Lack of Context:</strong> Generic CRUD endpoints do not provide the supplementary information necessary to accurately render forms with all required details.</li></ul><h3 id=\"hyperaction-api-decision-6\" tabindex=\"-1\">HyperAction API Decision</h3><p>In HyperAction API, we reconceptualize these operations as &quot;form loading&quot; and &quot;form saving&quot; to address these challenges:</p><ul><li><p><strong>Load Endpoint:</strong> When a form is opened, a load request is made. This endpoint returns the form model along with additional contextual data (such as ID-to-text mappings for drop-down fields) as a header object, ensuring that the form is pre-filled—either with existing data when editing an entry or with default preselected values when creating a new one—so it is ready for user interaction.</p></li><li><p><strong>Save Endpoint:</strong> When a user submits a form, the save endpoint accepts the same form model in its request. This approach ensures that the data, including any hidden identifiers, is consistently validated and processed.</p></li></ul><p>Both endpoints adhere to the batching principle, allowing multiple forms to be loaded or saved in a single request.</p><h4 id=\"example-6\" tabindex=\"-1\">Example</h4><p><strong>HTTP Request (Loading form)</strong></p><pre class=\"language-http\"><code class=\"language-http\"><span class=\"token request-line\"><span class=\"token method property\">GET</span> <span class=\"token request-target url\">/form/user-profile?ids=123,</span> <span class=\"token http-version property\">HTTP/1.1</span></span>\n<span class=\"token header\"><span class=\"token header-name keyword\">Host</span><span class=\"token punctuation\">:</span> <span class=\"token header-value\">api.example.com</span></span>\n<span class=\"token header\"><span class=\"token header-name keyword\">Accept</span><span class=\"token punctuation\">:</span> <span class=\"token header-value\">application/json</span></span></code></pre><p><strong>HTTP Response (Loading form)</strong></p><pre class=\"language-http\"><code class=\"language-http\"><span class=\"token response-status\"><span class=\"token http-version property\">HTTP/1.1</span> <span class=\"token status-code number\">200</span> <span class=\"token reason-phrase string\">OK</span></span>\n<span class=\"token header\"><span class=\"token header-name keyword\">Content-Type</span><span class=\"token punctuation\">:</span> <span class=\"token header-value\">application/json</span></span>\n<span class=\"token header\"><span class=\"token header-name keyword\">X-Count</span><span class=\"token punctuation\">:</span> <span class=\"token header-value\">2</span></span>\n<span class=\"token header\"><span class=\"token header-name keyword\">X-Total-Count</span><span class=\"token punctuation\">:</span> <span class=\"token header-value\">2</span></span>\n<span class=\"token header\"><span class=\"token header-name keyword\">X-Form</span><span class=\"token punctuation\">:</span> <span class=\"token header-value\">{ \"roleId\": { 5: \"Administrator\", 6: \"User\" } }</span></span>\n<span class=\"token application-json\">\n<span class=\"token punctuation\">[</span>\n\t<span class=\"token punctuation\">{</span>\n\t\t<span class=\"token string-property property\">\"userId\"</span><span class=\"token operator\">:</span> <span class=\"token number\">123</span><span class=\"token punctuation\">,</span>\n\t\t<span class=\"token string-property property\">\"etag\"</span><span class=\"token operator\">:</span> <span class=\"token string\">\"a1b2c3d4\"</span><span class=\"token punctuation\">,</span>\n\t\t<span class=\"token string-property property\">\"name\"</span><span class=\"token operator\">:</span> <span class=\"token string\">\"Alice\"</span><span class=\"token punctuation\">,</span>\n\t\t<span class=\"token string-property property\">\"email\"</span><span class=\"token operator\">:</span> <span class=\"token string\">\"alice@example.com\"</span><span class=\"token punctuation\">,</span>\n\t\t<span class=\"token string-property property\">\"roleId\"</span><span class=\"token operator\">:</span> <span class=\"token number\">5</span>\n\t<span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span>\n\t<span class=\"token punctuation\">{</span>\n\t\t<span class=\"token string-property property\">\"name\"</span><span class=\"token operator\">:</span> <span class=\"token string\">\"\"</span><span class=\"token punctuation\">,</span>\n\t\t<span class=\"token string-property property\">\"email\"</span><span class=\"token operator\">:</span> <span class=\"token string\">\"\"</span><span class=\"token punctuation\">,</span>\n\t\t<span class=\"token string-property property\">\"roleId\"</span><span class=\"token operator\">:</span> <span class=\"token number\">6</span>\n\t<span class=\"token punctuation\">}</span>\n<span class=\"token punctuation\">]</span>\n</span></code></pre><p><strong>HTTP Request (Saving form)</strong></p><pre class=\"language-http\"><code class=\"language-http\"><span class=\"token request-line\"><span class=\"token method property\">POST</span> <span class=\"token request-target url\">/form/user-profile</span> <span class=\"token http-version property\">HTTP/1.1</span></span>\n<span class=\"token header\"><span class=\"token header-name keyword\">Host</span><span class=\"token punctuation\">:</span> <span class=\"token header-value\">api.example.com</span></span>\n<span class=\"token header\"><span class=\"token header-name keyword\">Content-Type</span><span class=\"token punctuation\">:</span> <span class=\"token header-value\">application/json</span></span>\n<span class=\"token header\"><span class=\"token header-name keyword\">X-Idempotency-Key</span><span class=\"token punctuation\">:</span> <span class=\"token header-value\">123e4567-e89b-12d3-a456-426614174000</span></span>\n<span class=\"token application-json\">\n<span class=\"token punctuation\">[</span>\n\t<span class=\"token punctuation\">{</span>\n\t\t<span class=\"token string-property property\">\"userId\"</span><span class=\"token operator\">:</span> <span class=\"token number\">123</span><span class=\"token punctuation\">,</span>\n\t\t<span class=\"token string-property property\">\"etag\"</span><span class=\"token operator\">:</span> <span class=\"token string\">\"a1b2c3d4\"</span><span class=\"token punctuation\">,</span>\n\t\t<span class=\"token string-property property\">\"name\"</span><span class=\"token operator\">:</span> <span class=\"token string\">\"Alice Updated\"</span><span class=\"token punctuation\">,</span>\n\t\t<span class=\"token string-property property\">\"email\"</span><span class=\"token operator\">:</span> <span class=\"token string\">\"alice.new@example.com\"</span><span class=\"token punctuation\">,</span>\n\t\t<span class=\"token string-property property\">\"roleId\"</span><span class=\"token operator\">:</span> <span class=\"token number\">5</span>\n\t<span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span>\n\t<span class=\"token punctuation\">{</span>\n\t\t<span class=\"token string-property property\">\"userId\"</span><span class=\"token operator\">:</span> <span class=\"token number\">456</span><span class=\"token punctuation\">,</span>\n\t\t<span class=\"token string-property property\">\"name\"</span><span class=\"token operator\">:</span> <span class=\"token string\">\"Bob Created\"</span><span class=\"token punctuation\">,</span>\n\t\t<span class=\"token string-property property\">\"email\"</span><span class=\"token operator\">:</span> <span class=\"token string\">\"bob.created@example.com\"</span><span class=\"token punctuation\">,</span>\n\t\t<span class=\"token string-property property\">\"roleId\"</span><span class=\"token operator\">:</span> <span class=\"token number\">6</span>\n\t<span class=\"token punctuation\">}</span>\n<span class=\"token punctuation\">]</span>\n</span></code></pre><p>This dual-endpoint approach for form operations ensures that even simple data manipulations are handled with the same rigor and consistency as more complex business actions, aligning seamlessly with the overarching principles of the HyperAction API convention.</p><h2 id=\"8.-using-http-status-code-207-(multi-status)-for-batch-operations\" tabindex=\"-1\">8. Using HTTP Status Code 207 (Multi-Status) for Batch Operations</h2><h3 id=\"traditional-rest-approach-7\" tabindex=\"-1\">Traditional REST Approach</h3><p>Batch operations often return a generic <code>200 OK</code> even when different items in the batch yield mixed results, masking individual errors.</p><h3 id=\"challenges-7\" tabindex=\"-1\">Challenges</h3><p>A generic status code does not allow clients to easily identify which items in a batch succeeded or failed, complicating error handling and recovery.</p><h3 id=\"hyperaction-api-decision-7\" tabindex=\"-1\">HyperAction API Decision</h3><p>For operations that process batches with mixed outcomes, the API uses HTTP status code <strong>207 Multi-Status</strong>. The response body provides an array of per-item status information, giving clients granular feedback.</p><h4 id=\"example-7\" tabindex=\"-1\">Example</h4><p>A batch update of order statuses:</p><p><strong>HTTP Request</strong></p><pre class=\"language-http\"><code class=\"language-http\"><span class=\"token request-line\"><span class=\"token method property\">POST</span> <span class=\"token request-target url\">/order/update-status</span> <span class=\"token http-version property\">HTTP/1.1</span></span>\n<span class=\"token header\"><span class=\"token header-name keyword\">Host</span><span class=\"token punctuation\">:</span> <span class=\"token header-value\">api.example.com</span></span>\n<span class=\"token header\"><span class=\"token header-name keyword\">Content-Type</span><span class=\"token punctuation\">:</span> <span class=\"token header-value\">application/json</span></span>\n<span class=\"token application-json\">\n<span class=\"token punctuation\">[</span>\n\t<span class=\"token punctuation\">{</span> <span class=\"token string-property property\">\"orderId\"</span><span class=\"token operator\">:</span> <span class=\"token number\">201</span><span class=\"token punctuation\">,</span> <span class=\"token string-property property\">\"newStatus\"</span><span class=\"token operator\">:</span> <span class=\"token string\">\"shipped\"</span> <span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span>\n\t<span class=\"token punctuation\">{</span> <span class=\"token string-property property\">\"orderId\"</span><span class=\"token operator\">:</span> <span class=\"token number\">202</span><span class=\"token punctuation\">,</span> <span class=\"token string-property property\">\"newStatus\"</span><span class=\"token operator\">:</span> <span class=\"token string\">\"cancelled\"</span> <span class=\"token punctuation\">}</span>\n<span class=\"token punctuation\">]</span>\n</span></code></pre><p><strong>HTTP Response (207 Multi-Status)</strong></p><pre class=\"language-http\"><code class=\"language-http\"><span class=\"token response-status\"><span class=\"token http-version property\">HTTP/1.1</span> <span class=\"token status-code number\">207</span> <span class=\"token reason-phrase string\">Multi-Status</span></span>\n<span class=\"token header\"><span class=\"token header-name keyword\">Content-Type</span><span class=\"token punctuation\">:</span> <span class=\"token header-value\">application/json</span></span>\n<span class=\"token application-json\">\n<span class=\"token punctuation\">[</span>\n\t<span class=\"token punctuation\">{</span> <span class=\"token string-property property\">\"orderId\"</span><span class=\"token operator\">:</span> <span class=\"token number\">201</span><span class=\"token punctuation\">,</span> <span class=\"token string-property property\">\"status\"</span><span class=\"token operator\">:</span> <span class=\"token number\">200</span><span class=\"token punctuation\">,</span> <span class=\"token string-property property\">\"message\"</span><span class=\"token operator\">:</span> <span class=\"token string\">\"Status updated successfully\"</span> <span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span>\n\t<span class=\"token punctuation\">{</span> <span class=\"token string-property property\">\"orderId\"</span><span class=\"token operator\">:</span> <span class=\"token number\">202</span><span class=\"token punctuation\">,</span> <span class=\"token string-property property\">\"status\"</span><span class=\"token operator\">:</span> <span class=\"token number\">400</span><span class=\"token punctuation\">,</span> <span class=\"token string-property property\">\"message\"</span><span class=\"token operator\">:</span> <span class=\"token string\">\"Invalid transition to 'cancelled'\"</span> <span class=\"token punctuation\">}</span>\n<span class=\"token punctuation\">]</span>\n</span></code></pre><h2 id=\"9.-asynchronous-processing-for-long-running-operations\" tabindex=\"-1\">9. Asynchronous Processing for Long-Running Operations</h2><h3 id=\"traditional-rest-approach-8\" tabindex=\"-1\">Traditional REST Approach</h3><p>Long-running operations are typically processed synchronously, which may result in timeouts and a poor user experience.</p><h3 id=\"challenges-8\" tabindex=\"-1\">Challenges</h3><p>Holding a connection open during lengthy processing risks timeouts and duplicated requests, degrading both client and server performance.</p><h3 id=\"hyperaction-api-decision-8\" tabindex=\"-1\">HyperAction API Decision</h3><p>For operations that take an extended time, asynchronous processing is strongly encouraged. Two strategies are provided:</p><ol><li><strong>Callback URL:</strong> The client submits an array of requests with callback URLs. Once processed, the server calls the provided URL for each item with a minimal payload.</li><li><strong>Polling with Job ID:</strong> Alternatively, the server immediately returns a batch of job identifiers or URLs. Clients can then poll or use long-polling to track each job's progress.</li></ol><h4 id=\"example-(callback-url)\" tabindex=\"-1\">Example (Callback URL)</h4><p><strong>HTTP Request</strong></p><pre class=\"language-http\"><code class=\"language-http\"><span class=\"token request-line\"><span class=\"token method property\">POST</span> <span class=\"token request-target url\">/report/generate</span> <span class=\"token http-version property\">HTTP/1.1</span></span>\n<span class=\"token header\"><span class=\"token header-name keyword\">Host</span><span class=\"token punctuation\">:</span> <span class=\"token header-value\">api.example.com</span></span>\n<span class=\"token header\"><span class=\"token header-name keyword\">Content-Type</span><span class=\"token punctuation\">:</span> <span class=\"token header-value\">application/json</span></span>\n<span class=\"token application-json\">\n<span class=\"token punctuation\">[</span>\n\t<span class=\"token punctuation\">{</span>\n\t\t<span class=\"token string-property property\">\"reportType\"</span><span class=\"token operator\">:</span> <span class=\"token string\">\"sales\"</span><span class=\"token punctuation\">,</span>\n\t\t<span class=\"token string-property property\">\"parameters\"</span><span class=\"token operator\">:</span> <span class=\"token punctuation\">{</span> <span class=\"token string-property property\">\"date\"</span><span class=\"token operator\">:</span> <span class=\"token string\">\"2025-02-27\"</span> <span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span>\n\t\t<span class=\"token string-property property\">\"callbackUrl\"</span><span class=\"token operator\">:</span> <span class=\"token string\">\"https://client.example.com/report-callback\"</span>\n\t<span class=\"token punctuation\">}</span>\n<span class=\"token punctuation\">]</span>\n</span></code></pre><p><strong>HTTP Response</strong></p><pre class=\"language-http\"><code class=\"language-http\"><span class=\"token response-status\"><span class=\"token http-version property\">HTTP/1.1</span> <span class=\"token status-code number\">207</span> <span class=\"token reason-phrase string\">Multi-Status</span></span>\n<span class=\"token header\"><span class=\"token header-name keyword\">Content-Type</span><span class=\"token punctuation\">:</span> <span class=\"token header-value\">application/json</span></span>\n<span class=\"token application-json\">\n<span class=\"token punctuation\">[</span>\n\t<span class=\"token punctuation\">{</span> <span class=\"token string-property property\">\"jobId\"</span><span class=\"token operator\">:</span> <span class=\"token string\">\"abc123\"</span><span class=\"token punctuation\">,</span> <span class=\"token string-property property\">\"status\"</span><span class=\"token operator\">:</span> <span class=\"token number\">202</span> <span class=\"token punctuation\">}</span>\n<span class=\"token punctuation\">]</span>\n</span></code></pre><p>This decoupled approach improves reliability and scalability for long-running processes.</p><h2 id=\"10.-enforcing-idempotency\" tabindex=\"-1\">10. Enforcing Idempotency</h2><h3 id=\"traditional-rest-approach-9\" tabindex=\"-1\">Traditional REST Approach</h3><p>While certain HTTP methods are idempotent, operations that modify state (typically via POST) are not, leading to potential duplicate processing if retried.</p><h3 id=\"challenges-9\" tabindex=\"-1\">Challenges</h3><p>Without an explicit idempotency mechanism, clients risk accidentally processing the same request multiple times during network retries or failures.</p><h3 id=\"hyperaction-api-decision-9\" tabindex=\"-1\">HyperAction API Decision</h3><p>Every state-modifying action must include an idempotency key, passed as a header (e.g., <code>X-Idempotency-Key</code>). In line with the batching principle, the endpoint accepts an array of requests, each of which will be deduplicated based on the provided key.</p><h4 id=\"example-8\" tabindex=\"-1\">Example</h4><p><strong>HTTP Request</strong></p><pre class=\"language-http\"><code class=\"language-http\"><span class=\"token request-line\"><span class=\"token method property\">POST</span> <span class=\"token request-target url\">/account/update-email</span> <span class=\"token http-version property\">HTTP/1.1</span></span>\n<span class=\"token header\"><span class=\"token header-name keyword\">Host</span><span class=\"token punctuation\">:</span> <span class=\"token header-value\">api.example.com</span></span>\n<span class=\"token header\"><span class=\"token header-name keyword\">Content-Type</span><span class=\"token punctuation\">:</span> <span class=\"token header-value\">application/json</span></span>\n<span class=\"token header\"><span class=\"token header-name keyword\">X-Idempotency-Key</span><span class=\"token punctuation\">:</span> <span class=\"token header-value\">4f2a9c3e-d8f1-4b5e-9a2d-123456789abc</span></span>\n<span class=\"token application-json\">\n<span class=\"token punctuation\">[</span>\n\t<span class=\"token punctuation\">{</span>\n\t\t<span class=\"token string-property property\">\"userId\"</span><span class=\"token operator\">:</span> <span class=\"token number\">789</span><span class=\"token punctuation\">,</span>\n\t\t<span class=\"token string-property property\">\"email\"</span><span class=\"token operator\">:</span> <span class=\"token string\">\"new.email@example.com\"</span>\n\t<span class=\"token punctuation\">}</span>\n<span class=\"token punctuation\">]</span>\n</span></code></pre><p>The server uses the idempotency key to ensure that duplicate requests do not cause repeated processing.</p><h2 id=\"11.-caching-and-consistency-with-etags\" tabindex=\"-1\">11. Caching and Consistency with ETAGs</h2><h3 id=\"traditional-rest-approach-10\" tabindex=\"-1\">Traditional REST Approach</h3><p>ETAGs are used for caching and concurrency but are often applied at a coarse level (e.g., per entire response), which is insufficient when handling batches.</p><h3 id=\"challenges-10\" tabindex=\"-1\">Challenges</h3><p>Without item-level ETAGs, clients may inadvertently overwrite data when updating parts of a batch, risking consistency issues.</p><h3 id=\"hyperaction-api-decision-10\" tabindex=\"-1\">HyperAction API Decision</h3><p>Assign ETAGs to each individual item in a response. When updating items, the client includes the corresponding ETAG for each item in the batch, ensuring updates are based on the latest state.</p><h4 id=\"example-9\" tabindex=\"-1\">Example</h4><p>Retrieving items with individual ETAGs:</p><p><strong>HTTP Request</strong></p><pre class=\"language-http\"><code class=\"language-http\"><span class=\"token request-line\"><span class=\"token method property\">GET</span> <span class=\"token request-target url\">/item?limit=10&amp;offset=0</span> <span class=\"token http-version property\">HTTP/1.1</span></span>\n<span class=\"token header\"><span class=\"token header-name keyword\">Host</span><span class=\"token punctuation\">:</span> <span class=\"token header-value\">api.example.com</span></span>\n<span class=\"token header\"><span class=\"token header-name keyword\">Accept</span><span class=\"token punctuation\">:</span> <span class=\"token header-value\">application/json</span></span></code></pre><p><strong>HTTP Response</strong></p><pre class=\"language-http\"><code class=\"language-http\"><span class=\"token response-status\"><span class=\"token http-version property\">HTTP/1.1</span> <span class=\"token status-code number\">200</span> <span class=\"token reason-phrase string\">OK</span></span>\n<span class=\"token header\"><span class=\"token header-name keyword\">Content-Type</span><span class=\"token punctuation\">:</span> <span class=\"token header-value\">application/json</span></span>\n<span class=\"token header\"><span class=\"token header-name keyword\">X-Count</span><span class=\"token punctuation\">:</span> <span class=\"token header-value\">10</span></span>\n<span class=\"token header\"><span class=\"token header-name keyword\">X-Limit</span><span class=\"token punctuation\">:</span> <span class=\"token header-value\">10</span></span>\n<span class=\"token header\"><span class=\"token header-name keyword\">X-Offset</span><span class=\"token punctuation\">:</span> <span class=\"token header-value\">0</span></span>\n<span class=\"token application-json\">\n<span class=\"token punctuation\">[</span>\n\t<span class=\"token punctuation\">{</span> <span class=\"token string-property property\">\"id\"</span><span class=\"token operator\">:</span> <span class=\"token number\">123</span><span class=\"token punctuation\">,</span> <span class=\"token string-property property\">\"name\"</span><span class=\"token operator\">:</span> <span class=\"token string\">\"Item One\"</span><span class=\"token punctuation\">,</span> <span class=\"token string-property property\">\"etag\"</span><span class=\"token operator\">:</span> <span class=\"token string\">\"a1b2c3d4\"</span> <span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span>\n\t<span class=\"token punctuation\">{</span> <span class=\"token string-property property\">\"id\"</span><span class=\"token operator\">:</span> <span class=\"token number\">124</span><span class=\"token punctuation\">,</span> <span class=\"token string-property property\">\"name\"</span><span class=\"token operator\">:</span> <span class=\"token string\">\"Item Two\"</span><span class=\"token punctuation\">,</span> <span class=\"token string-property property\">\"etag\"</span><span class=\"token operator\">:</span> <span class=\"token string\">\"e5f6g7h8\"</span> <span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span>\n\t<span class=\"token operator\">...</span>\n<span class=\"token punctuation\">]</span>\n</span></code></pre><p>When updating, the request carries an array of items with ETAGs:</p><p><strong>HTTP Request</strong></p><pre class=\"language-http\"><code class=\"language-http\"><span class=\"token request-line\"><span class=\"token method property\">PUT</span> <span class=\"token request-target url\">/item</span> <span class=\"token http-version property\">HTTP/1.1</span></span>\n<span class=\"token header\"><span class=\"token header-name keyword\">Host</span><span class=\"token punctuation\">:</span> <span class=\"token header-value\">api.example.com</span></span>\n<span class=\"token header\"><span class=\"token header-name keyword\">Content-Type</span><span class=\"token punctuation\">:</span> <span class=\"token header-value\">application/json</span></span>\n<span class=\"token application-json\">\n<span class=\"token punctuation\">[</span>\n\t<span class=\"token punctuation\">{</span> <span class=\"token string-property property\">\"id\"</span><span class=\"token operator\">:</span> <span class=\"token number\">123</span><span class=\"token punctuation\">,</span> <span class=\"token string-property property\">\"name\"</span><span class=\"token operator\">:</span> <span class=\"token string\">\"Item One Updated\"</span><span class=\"token punctuation\">,</span> <span class=\"token string-property property\">\"etag\"</span><span class=\"token operator\">:</span> <span class=\"token string\">\"a1b2c3d4\"</span> <span class=\"token punctuation\">}</span>\n<span class=\"token punctuation\">]</span>\n</span></code></pre><p>This mechanism minimizes race conditions and guarantees consistency for batch operations.</p><h2 id=\"12.-no-api-versioning-in-urls\" tabindex=\"-1\">12. No API Versioning in URLs</h2><h3 id=\"traditional-rest-approach-11\" tabindex=\"-1\">Traditional REST Approach</h3><p>Version numbers are commonly embedded in URLs (e.g., <code>/v1/users</code>, <code>v1.example.com/users</code>), which can lead to fragmentation and maintenance overhead.</p><h3 id=\"challenges-11\" tabindex=\"-1\">Challenges</h3><p>Including version numbers in the URL clutters the design and complicates the evolution of the API, especially when changes are either mandatory or better expressed through distinct endpoints.</p><p>Version numbers embedded in URLs tend to clutter the API design and force clients to hardcode endpoint paths that may rapidly become outdated. By removing version numbers from the URL and instead leveraging HTTP headers—such as the <code>Accept</code> and <code>Content-Type</code> headers—for payload negotiation, the API remains clean and adaptable. This strategy allows changes to be introduced without breaking existing client implementations, as the evolution of data formats or business rules can be communicated through header negotiation rather than through separate endpoint versions.</p><p>Additionally, relying on versioned endpoints can inadvertently signal that a fundamental business process has changed while still supporting outdated behavior. When a new version of an endpoint introduces breaking changes, it implies that the underlying business process has evolved. In such cases, maintaining the old version not only creates confusion but also contradicts the notion of a genuine process change. Either the business process has indeed changed—rendering the old endpoint obsolete and in need of removal—or a completely new business process is introduced, which should be reflected through distinct endpoint naming rather than a simple version increment.</p><h3 id=\"hyperaction-api-decision-11\" tabindex=\"-1\">HyperAction API Decision</h3><p>Version numbers are removed from URLs. Instead, use HTTP headers such as <code>Accept</code> and <code>Content-Type</code> to indicate the payload format or model variant, if necessary. This keeps the endpoint clean and directs versioning concerns to header negotiations.</p><ul><li><a href=\"https://www.hmeid.com/blog/just-say-no-to-versioning\" target=\"_blank\" rel=\"noopener noreferrer\">Just say no - to versioning APIs</a></li><li><a href=\"https://www.infoq.com/news/2016/07/web-api-versioning/\" target=\"_blank\" rel=\"noopener noreferrer\">Don't Version Your Web API</a></li><li><a href=\"https://av.tib.eu/media/48857\" target=\"_blank\" rel=\"noopener noreferrer\">Versions are evil</a></li><li><a href=\"https://www.ben-morris.com/rest-apis-dont-need-a-versioning-strategy-they-need-a-change-strategy/\" target=\"_blank\" rel=\"noopener noreferrer\">REST APIs don’t need a versioning strategy - they need a change strategy</a></li></ul><h4 id=\"example-10\" tabindex=\"-1\">Example</h4><p>Consider a client request to the <code>/account/summary</code> endpoint that negotiates the desired payload format via the <code>Content-Type</code> and <code>Accept</code> headers rather than embedding a version number in the URL:</p><p><strong>HTTP Request</strong></p><pre class=\"language-http\"><code class=\"language-http\"><span class=\"token request-line\"><span class=\"token method property\">GET</span> <span class=\"token request-target url\">/account/summary</span> <span class=\"token http-version property\">HTTP/1.1</span></span>\n<span class=\"token header\"><span class=\"token header-name keyword\">Host</span><span class=\"token punctuation\">:</span> <span class=\"token header-value\">api.example.com</span></span>\n<span class=\"token header\"><span class=\"token header-name keyword\">Accept</span><span class=\"token punctuation\">:</span> <span class=\"token header-value\">application/vnd.example.account-summary-with-balance+json</span></span></code></pre><p>In a successful scenario, the server recognizes the specified version in the headers and responds with the corresponding data model:</p><p><strong>Successful HTTP Response</strong></p><pre class=\"language-http\"><code class=\"language-http\"><span class=\"token response-status\"><span class=\"token http-version property\">HTTP/1.1</span> <span class=\"token status-code number\">200</span> <span class=\"token reason-phrase string\">OK</span></span>\n<span class=\"token header\"><span class=\"token header-name keyword\">Content-Type</span><span class=\"token punctuation\">:</span> <span class=\"token header-value\">application/vnd.example.account-summary-with-balance+json</span></span>\n<span class=\"token header\"><span class=\"token header-name keyword\">X-API-Status</span><span class=\"token punctuation\">:</span> <span class=\"token header-value\">Active</span></span>\n<span class=\"token application-json\">\n<span class=\"token punctuation\">[</span>\n\t<span class=\"token punctuation\">{</span>\n\t\t<span class=\"token string-property property\">\"accountId\"</span><span class=\"token operator\">:</span> <span class=\"token number\">123</span><span class=\"token punctuation\">,</span>\n\t\t<span class=\"token string-property property\">\"name\"</span><span class=\"token operator\">:</span> <span class=\"token string\">\"Alice\"</span><span class=\"token punctuation\">,</span>\n\t\t<span class=\"token string-property property\">\"balance\"</span><span class=\"token operator\">:</span> <span class=\"token number\">250.75</span>\n\t<span class=\"token punctuation\">}</span>\n<span class=\"token punctuation\">]</span>\n</span></code></pre><p>Alternatively, if the client submits a request using a deprecated accept type (for example, <code>application/vnd.example.account-summary+json</code>), the server can indicate that this request model is no longer supported. This response clearly communicates that the API no longer supports the outdated version, urging the client to update its request headers:</p><p><strong>Deprecated Request HTTP Response</strong></p><pre class=\"language-http\"><code class=\"language-http\"><span class=\"token response-status\"><span class=\"token http-version property\">HTTP/1.1</span> <span class=\"token status-code number\">410</span> <span class=\"token reason-phrase string\">Gone</span></span>\n<span class=\"token header\"><span class=\"token header-name keyword\">Content-Type</span><span class=\"token punctuation\">:</span> <span class=\"token header-value\">application/json</span></span>\n<span class=\"token header\"><span class=\"token header-name keyword\">X-API-Status</span><span class=\"token punctuation\">:</span> <span class=\"token header-value\">Deprecated</span></span>\n<span class=\"token application-json\">\n<span class=\"token punctuation\">{</span>\n\t<span class=\"token string-property property\">\"error\"</span><span class=\"token operator\">:</span> <span class=\"token string\">\"Not Acceptable\"</span><span class=\"token punctuation\">,</span>\n\t<span class=\"token string-property property\">\"message\"</span><span class=\"token operator\">:</span> <span class=\"token string\">\"The requested content type 'application/vnd.example.account-summary+json' is deprecated and no longer supported. Please update your Accept header to a supported version.\"</span>\n<span class=\"token punctuation\">}</span>\n</span></code></pre><p>It is also acceptable for the server to respond with just the appropriate headers and a <code>410</code> status code, without any accompanying content.</p><p>Note that a draft standardizing the use of a <code>Deprecation</code> header in HTTP responses is currently underway, as described in <a href=\"https://datatracker.ietf.org/doc/draft-ietf-httpapi-deprecation-header/\" target=\"_blank\" rel=\"noopener noreferrer\">The Deprecation HTTP Header Field draft</a>, and is still in active development at the time of writing this article.</p><h2 id=\"conclusion\" tabindex=\"-1\">Conclusion</h2><p>The <strong>HyperAction API</strong> convention represents a deliberate reimagining of HTTP API design by enforcing batch processing at every endpoint and a singular naming convention throughout. By eliminating single-item endpoints, abstract pagination models, and generic resource endpoints—and by always using singular names—the API design closely aligns with the underlying business logic and operational realities. With every endpoint accepting and returning arrays of items, employing dedicated HTTP headers for metadata, explicit idempotency keys, and supporting asynchronous operations, the HyperAction API provides a robust, efficient, and clear contract between clients and servers.</p><p>Software engineers adopting these guidelines will benefit from clearer API semantics, reduced redundancy in client–server communication, and an interface that naturally mirrors the business operations it supports. This document serves as a comprehensive technical reference for designing modern HTTP APIs under the HyperAction API convention.</p><blockquote><h3 id=\"update-(july-2026)%3A-the-http-query-method-(rfc-10008)\" tabindex=\"-1\">Update (July 2026): The HTTP <code>QUERY</code> Method (RFC 10008)</h3><p>A year after the original publication of this article, <a href=\"https://datatracker.ietf.org/doc/html/rfc10008\" target=\"_blank\" rel=\"noopener noreferrer\">RFC 10008</a> officially standardized the <strong><code>QUERY</code></strong> HTTP method. This standard aligns exceptionally well with the HyperAction API architecture.</p><p><code>QUERY</code> provides a safe, idempotent request method that natively supports a request body—effectively bridging the gap where <code>GET</code> fell short. Under this updated model, HyperAction APIs can strictly categorize endpoints into two primary behaviors:</p><ul><li><strong>Read-Only Operations (<code>QUERY</code>):</strong> Used for complex data retrieval, search filtering, and batch fetching that require intricate request payloads without altering server state.</li><li><strong>State-Modifying Operations (<code>POST</code>):</strong> Reserved exclusively for data-modifying business actions, command execution, and state changes.</li></ul><p><strong>Adoption Considerations &amp; Friction:</strong> While <code>QUERY</code> is the ideal protocol-level choice for read-heavy batch operations, full adoption introduces temporary operational friction. Many legacy client/server frameworks, API gateways, and WAFs do not yet support the <code>QUERY</code> verb natively or may strip payload bodies from non-<code>POST</code> requests. As framework ecosystems and infrastructure catch up over time, <code>GET</code> with URL parameters remains a fallback, but <code>QUERY</code> represents the long-term target for modern HyperAction API implementations.</p></blockquote><h2 id=\"future-work-%26-feedback\" tabindex=\"-1\">Future Work &amp; Feedback</h2><p>Please note that this whole concept is still a work in progress. I would be very happy to receive any feedback or suggestions to help refine and improve this approach.</p><h2 id=\"appendix%3A-embracing-vertical-slice-architecture\" tabindex=\"-1\">Appendix: Embracing Vertical Slice Architecture</h2><p>The protocol conventions presented in this document are partly inspired by the principles of <a href=\"https://www.jimmybogard.com/vertical-slice-architecture/\" target=\"_blank\" rel=\"noopener noreferrer\">vertical slice architecture</a>. While this article focuses on redefining HTTP request-response protocols—such as action-oriented endpoints, batch processing, and explicit metadata handling—the underlying inspiration comes from an architectural mindset that emphasizes self-contained business features.</p><p>Vertical slice architecture is a holistic approach to software organization. It advocates for encapsulating all layers of functionality—ranging from user interface and application logic to data access—within discrete slices that correspond to individual business capabilities. This separation minimizes interdependencies and simplifies testing, maintenance, and independent evolution of features.</p><p>It is important to note that this document addresses only the HTTP protocol conventions, not the deeper aspects of code organization. The protocol decisions described here are fully compatible with, and indeed inspired by, a vertical slice approach. However, the specifics of organizing your codebase into vertical slices—covering topics such as module boundaries, dependency management, and internal service communication—fall outside the scope of this article.</p><p>We plan to publish a separate article in the future that will explore these code organization topics in detail, providing practical guidance on how to implement vertical slice architecture to complement the protocol conventions outlined here.</p>",
      "date_published": "2025-03-11T00:00:00Z"
    }
    ,
    {
      "id": "https://byteaether.github.io/2025/announcing-querylink-v100-simplify-your-ui-and-data-integration/",
      "url": "https://byteaether.github.io/2025/announcing-querylink-v100-simplify-your-ui-and-data-integration/",
      "title": "Announcing QueryLink v1.0.0: Simplify Your UI and Data Integration",
      "content_html": "<p>We are thrilled to announce the official release of <strong><a href=\"https://github.com/ByteAether/QueryLink/\" target=\"_blank\" rel=\"noopener noreferrer\">QueryLink v1.0.0</a></strong>! This milestone release marks the culmination of our efforts to provide a seamless bridge between UI components and backend data sources powered by <code>IQueryable</code>. Whether you're building dynamic data grids or data tables, QueryLink is here to <a href=\"https://byteaether.github.io/2025/seamlessly-connecting-frontend-and-backend-data-in-net-applications-a-new-paradigm-for-effortless-integration/\">simplify your life</a> by handling filters, sorting, and query string conversions with minimal code.</p><h2 id=\"what-is-querylink%3F\" tabindex=\"-1\">What is QueryLink?</h2><p>QueryLink is a <a href=\"https://www.nuget.org/packages/ByteAether.QueryLink/\" target=\"_blank\" rel=\"noopener noreferrer\">NuGet package</a> designed to integrate UI components (like datagrids and datatables) with backend <code>IQueryable</code> data sources. It helps you to:</p><ul><li>Define and apply complex filters and sort orders effortlessly.</li><li>Override default behaviors with custom expressions.</li><li>Convert filtering and sorting definitions to and from query strings for smooth HTTP API integration.</li></ul><p>By abstracting these common tasks, QueryLink lets you focus on building great applications without getting bogged down in repetitive query logic.</p><h2 id=\"key-features\" tabindex=\"-1\">Key Features</h2><ul><li><strong>Filter Definitions:</strong> Easily define filters using a range of operators (e.g., equals, not equals, greater than, contains, etc.) to refine your queries.</li><li><strong>Order Definitions:</strong> Specify sorting rules to control how your data is displayed.</li><li><strong>Expression-Based Overrides:</strong> Customize default filter and order operations to suit your application's needs.</li><li><strong>Query String Conversion:</strong> Convert definitions to and from query strings, making it straightforward to pass parameters via HTTP requests.</li><li><strong>IQueryable Extensions:</strong> Directly apply filter and order definitions to your <code>IQueryable</code> data sources.</li></ul><p>With support for .NET 6.0, .NET 8.0, and .NET Standard 2.1, QueryLink fits neatly into your modern .NET projects.</p><h2 id=\"installation\" tabindex=\"-1\">Installation</h2><p>Getting started is as simple as running a single command. Install the latest stable version of QueryLink via NuGet:</p><pre class=\"language-sh\"><code class=\"language-sh\">dotnet <span class=\"token function\">add</span> package ByteAether.QueryLink</code></pre><p>If you need a preview version, you can specify it with the <code>--version</code> option. This ease-of-installation helps you to integrate QueryLink into your project quickly and reliably.</p><h2 id=\"how-to-use-querylink\" tabindex=\"-1\">How to Use QueryLink</h2><p>QueryLink provides a few core concepts that make filtering and sorting a breeze:</p><h3 id=\"1.-defining-filters-and-orders\" tabindex=\"-1\">1. Defining Filters and Orders</h3><p>The <code>Definitions</code> class allows you to specify filter and order criteria. For example:</p><pre class=\"language-csharp\"><code class=\"language-csharp\"><span class=\"token class-name\"><span class=\"token keyword\">var</span></span> definitions <span class=\"token operator\">=</span> <span class=\"token keyword\">new</span> <span class=\"token constructor-invocation class-name\">Definitions</span>\n<span class=\"token punctuation\">{</span>\n\tFilters <span class=\"token operator\">=</span> <span class=\"token keyword\">new</span> <span class=\"token constructor-invocation class-name\">List<span class=\"token punctuation\">&lt;</span>FilterDefinition<span class=\"token punctuation\">></span></span>\n\t<span class=\"token punctuation\">{</span>\n\t\t<span class=\"token keyword\">new</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"Name\"</span><span class=\"token punctuation\">,</span> FilterOperator<span class=\"token punctuation\">.</span>Eq<span class=\"token punctuation\">,</span> <span class=\"token string\">\"John\"</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">,</span>\n\t\t<span class=\"token keyword\">new</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"Age\"</span><span class=\"token punctuation\">,</span> FilterOperator<span class=\"token punctuation\">.</span>Gt<span class=\"token punctuation\">,</span> <span class=\"token number\">30</span><span class=\"token punctuation\">)</span>\n\t<span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span>\n\tOrders <span class=\"token operator\">=</span> <span class=\"token keyword\">new</span> <span class=\"token constructor-invocation class-name\">List<span class=\"token punctuation\">&lt;</span>OrderDefinition<span class=\"token punctuation\">></span></span>\n\t<span class=\"token punctuation\">{</span>\n\t\t<span class=\"token keyword\">new</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"Name\"</span><span class=\"token punctuation\">,</span> <span class=\"token named-parameter punctuation\">IsReversed</span><span class=\"token punctuation\">:</span> <span class=\"token boolean\">false</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">,</span>\n\t\t<span class=\"token keyword\">new</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"Age\"</span><span class=\"token punctuation\">,</span> <span class=\"token named-parameter punctuation\">IsReversed</span><span class=\"token punctuation\">:</span> <span class=\"token boolean\">true</span><span class=\"token punctuation\">)</span>\n\t<span class=\"token punctuation\">}</span>\n<span class=\"token punctuation\">}</span><span class=\"token punctuation\">;</span></code></pre><p>This snippet creates filters to select records where the name equals &quot;John&quot; and age is greater than 30, and then orders the results accordingly.</p><h3 id=\"2.-using-overrides-for-custom-behavior\" tabindex=\"-1\">2. Using Overrides for Custom Behavior</h3><p>Sometimes the default filtering logic isn't enough. QueryLink's <code>Overrides</code> class lets you tailor operations using expression-based overrides:</p><pre class=\"language-csharp\"><code class=\"language-csharp\"><span class=\"token class-name\"><span class=\"token keyword\">var</span></span> overrides <span class=\"token operator\">=</span> <span class=\"token keyword\">new</span> <span class=\"token constructor-invocation class-name\">Overrides</span>\n<span class=\"token punctuation\">{</span>\n\tFilter <span class=\"token operator\">=</span> <span class=\"token keyword\">new</span> <span class=\"token constructor-invocation class-name\">List<span class=\"token punctuation\">&lt;</span>FilterOverride<span class=\"token punctuation\">></span></span>\n\t<span class=\"token punctuation\">{</span>\n\t\t<span class=\"token keyword\">new</span><span class=\"token punctuation\">(</span>p <span class=\"token operator\">=></span> p<span class=\"token punctuation\">.</span>Name<span class=\"token punctuation\">,</span> p <span class=\"token operator\">=></span> p<span class=\"token punctuation\">.</span>FullName<span class=\"token punctuation\">)</span>\n\t<span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span>\n\tOrder <span class=\"token operator\">=</span> <span class=\"token keyword\">new</span> <span class=\"token constructor-invocation class-name\">List<span class=\"token punctuation\">&lt;</span>OrderOverride<span class=\"token punctuation\">></span></span>\n\t<span class=\"token punctuation\">{</span>\n\t\t<span class=\"token keyword\">new</span><span class=\"token punctuation\">(</span>p <span class=\"token operator\">=></span> p<span class=\"token punctuation\">.</span>Name<span class=\"token punctuation\">,</span> p <span class=\"token operator\">=></span> p<span class=\"token punctuation\">.</span>FullName<span class=\"token punctuation\">)</span>\n\t<span class=\"token punctuation\">}</span>\n<span class=\"token punctuation\">}</span><span class=\"token punctuation\">;</span></code></pre><p>This approach lets you substitute a property (like <code>Name</code>) with another (like <code>FullName</code>) when applying filters or orders, offering flexible customization.</p><h3 id=\"3.-converting-to-and-from-query-strings\" tabindex=\"-1\">3. Converting to and from Query Strings</h3><p>Easily integrate with web APIs by converting your definitions to a query string and back:</p><pre class=\"language-csharp\"><code class=\"language-csharp\"><span class=\"token class-name\"><span class=\"token keyword\">string</span></span> queryString <span class=\"token operator\">=</span> definitions<span class=\"token punctuation\">.</span><span class=\"token function\">ToQueryString</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token class-name\">Definitions</span> parsedDefinitions <span class=\"token operator\">=</span> Definitions<span class=\"token punctuation\">.</span><span class=\"token function\">FromQueryString</span><span class=\"token punctuation\">(</span>queryString<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span></code></pre><p>This feature ensures that your filtering and sorting logic is easily transferable over HTTP.</p><h3 id=\"4.-applying-definitions-to-iqueryable\" tabindex=\"-1\">4. Applying Definitions to IQueryable</h3><p>Finally, apply your definitions directly to an <code>IQueryable</code> source:</p><pre class=\"language-csharp\"><code class=\"language-csharp\"><span class=\"token class-name\">IQueryable</span> query <span class=\"token operator\">=</span> dbContext<span class=\"token punctuation\">.</span>People<span class=\"token punctuation\">.</span><span class=\"token function\">AsQueryable</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\nquery <span class=\"token operator\">=</span> query<span class=\"token punctuation\">.</span><span class=\"token function\">Apply</span><span class=\"token punctuation\">(</span>definitions<span class=\"token punctuation\">,</span> overrides<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span></code></pre><p>By extending <code>IQueryable</code>, QueryLink seamlessly integrates with your existing data-access code, providing a clean and efficient query transformation pipeline.</p><h2 id=\"real-world-examples\" tabindex=\"-1\">Real-World Examples</h2><p>QueryLink shines in practical scenarios—like integrating with a MudBlazor DataGrid alongside EF Core. The package simplifies server-side data loading by reading grid state, generating query strings, and applying filter and order definitions to your data source. This comprehensive approach means fewer lines of code and less boilerplate for you to manage.</p><h2 id=\"join-the-community\" tabindex=\"-1\">Join the Community</h2><p>We believe that community feedback is invaluable. As we celebrate this stable release, we invite you to contribute to the project:</p><ul><li><strong>Submit Issues or Feature Requests:</strong> Help us make QueryLink even better.</li><li><strong>Contribute Code:</strong> Fork the repository, implement improvements, and send us a pull request.</li><li><strong>Spread the Word:</strong> Share your experiences using QueryLink on social media and developer forums.</li></ul><p>Your contributions and insights will help shape the future of QueryLink.</p><h2 id=\"license\" tabindex=\"-1\">License</h2><p>QueryLink is released under the MIT License, ensuring that it remains free and open for your use and customization. For more details, please refer to the LICENSE file in the repository.</p><hr/><p><a href=\"https://github.com/ByteAether/QueryLink/\" target=\"_blank\" rel=\"noopener noreferrer\">QueryLink v1.0.0</a> is here to make your UI and data integration simpler, more flexible, and efficient. We look forward to seeing the innovative solutions you build with it. Happy coding!</p>",
      "date_published": "2025-03-04T00:00:00Z"
    }
    ,
    {
      "id": "https://byteaether.github.io/2025/harnessing-weak-referenced-event-managers-and-publish-subscribe-patterns-in-net/",
      "url": "https://byteaether.github.io/2025/harnessing-weak-referenced-event-managers-and-publish-subscribe-patterns-in-net/",
      "title": "Harnessing Weak-Referenced Event Managers and Publish–Subscribe Patterns in .NET",
      "content_html": "<p>In modern application development, event-driven programming is ubiquitous. Whether you're building rich desktop applications with WPF or web applications with Blazor, events provide a powerful way to decouple components. However, one common pitfall in event-based systems is memory leaks caused by lingering event subscriptions. The weak event pattern, along with publish–subscribe architectures, offers a robust solution by using weak references to hold subscribers. This ensures that objects can be garbage collected when no longer needed, even if they remain subscribed to events.</p><p>In this article, we will explore the theory behind weak-referenced event managers, demonstrate their practical implementation in .NET using C#, and illustrate how these concepts can be applied to real-world scenarios—including a look at the <a href=\"https://github.com/ByteAether/WeakEvent\" target=\"_blank\" rel=\"noopener noreferrer\">ByteAether.WeakEvent library</a>.</p><h2 id=\"understanding-the-weak-event-pattern\" tabindex=\"-1\">Understanding the Weak Event Pattern</h2><h3 id=\"what-is-a-weak-event%3F\" tabindex=\"-1\">What Is a Weak Event?</h3><p>In the typical event subscription model in .NET, the publisher holds a strong reference to each subscriber via its delegate invocation list. If a subscriber does not explicitly unsubscribe, it will remain in memory—even when it's no longer needed—causing memory leaks. The weak event pattern addresses this issue by holding references to subscribers weakly. In simple terms, a weak reference allows the garbage collector to reclaim the subscriber's memory if no other strong references exist.</p><p>According to Microsoft's documentation on <a href=\"https://learn.microsoft.com/en-us/dotnet/desktop/wpf/events/weak-event-patterns?view=netdesktop-9.0\" target=\"_blank\" rel=\"noopener noreferrer\">WPF weak event patterns</a>, the weak event pattern is particularly valuable in scenarios where the publisher's lifetime is longer than that of its subscribers. This pattern minimizes the risk of memory leaks by decoupling the event's lifecycle from its subscribers.</p><h3 id=\"key-benefits\" tabindex=\"-1\">Key Benefits</h3><ul><li><strong>Memory Efficiency:</strong> By using weak references, subscribers do not prevent garbage collection, thereby reducing memory bloat.</li><li><strong>Decoupled Design:</strong> Publishers and subscribers can exist independently. This decoupling facilitates more maintainable code.</li><li><strong>Automatic Cleanup:</strong> There is less need for manual unsubscription, which reduces the risk of human error leading to memory leaks.</li><li><strong>Versatility:</strong> While particularly useful in UI frameworks (WPF, Blazor), the pattern is applicable in any event-driven architecture.</li></ul><h2 id=\"deep-dive-into-.net-garbage-collection-and-weak-references\" tabindex=\"-1\">Deep Dive into .NET Garbage Collection and Weak References</h2><h3 id=\"the-role-of-the-garbage-collector\" tabindex=\"-1\">The Role of the Garbage Collector</h3><p>The .NET garbage collector (GC) is responsible for managing memory automatically. It frees objects that are no longer referenced by the application. However, in traditional event subscriptions, the publisher's reference to the subscriber via the event delegate prevents the GC from collecting the subscriber—even if the subscriber is no longer actively used.</p><h3 id=\"how-weak-references-work\" tabindex=\"-1\">How Weak References Work</h3><p>A weak reference allows an object to be referenced without preventing its collection by the GC. Microsoft explains in its <a href=\"https://learn.microsoft.com/en-us/dotnet/standard/garbage-collection/weak-references\" target=\"_blank\" rel=\"noopener noreferrer\">.NET garbage collection documentation on weak references</a> that a weak reference object does not count as a &quot;root&quot; for garbage collection. This means that if the only references to an object are weak, the GC can reclaim that object's memory.</p><h4 id=\"example%3A-using-weakreference-in-c%23\" tabindex=\"-1\">Example: Using WeakReference in C#</h4><pre class=\"language-csharp\"><code class=\"language-csharp\"><span class=\"token keyword\">public</span> <span class=\"token keyword\">class</span> <span class=\"token class-name\">MySubscriber</span>\n<span class=\"token punctuation\">{</span>\n\t<span class=\"token keyword\">public</span> <span class=\"token return-type class-name\"><span class=\"token keyword\">void</span></span> <span class=\"token function\">HandleEvent</span><span class=\"token punctuation\">(</span><span class=\"token class-name\"><span class=\"token keyword\">object</span></span> sender<span class=\"token punctuation\">,</span> <span class=\"token class-name\">EventArgs</span> e<span class=\"token punctuation\">)</span>\n\t<span class=\"token punctuation\">{</span>\n\t\tConsole<span class=\"token punctuation\">.</span><span class=\"token function\">WriteLine</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"Event handled by MySubscriber.\"</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\t<span class=\"token punctuation\">}</span>\n<span class=\"token punctuation\">}</span>\n\n<span class=\"token keyword\">public</span> <span class=\"token keyword\">class</span> <span class=\"token class-name\">Publisher</span>\n<span class=\"token punctuation\">{</span>\n\t<span class=\"token comment\">// List of weak references to event handlers</span>\n\t<span class=\"token keyword\">private</span> <span class=\"token class-name\">List<span class=\"token punctuation\">&lt;</span>WeakReference<span class=\"token punctuation\">&lt;</span>EventHandler<span class=\"token punctuation\">></span><span class=\"token punctuation\">></span></span> _handlers <span class=\"token operator\">=</span> <span class=\"token keyword\">new</span> <span class=\"token constructor-invocation class-name\">List<span class=\"token punctuation\">&lt;</span>WeakReference<span class=\"token punctuation\">&lt;</span>EventHandler<span class=\"token punctuation\">></span><span class=\"token punctuation\">></span></span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\n\t<span class=\"token keyword\">public</span> <span class=\"token return-type class-name\"><span class=\"token keyword\">void</span></span> <span class=\"token function\">Subscribe</span><span class=\"token punctuation\">(</span><span class=\"token class-name\">EventHandler</span> handler<span class=\"token punctuation\">)</span>\n\t<span class=\"token punctuation\">{</span>\n\t\t_handlers<span class=\"token punctuation\">.</span><span class=\"token function\">Add</span><span class=\"token punctuation\">(</span><span class=\"token keyword\">new</span> <span class=\"token constructor-invocation class-name\">WeakReference<span class=\"token punctuation\">&lt;</span>EventHandler<span class=\"token punctuation\">></span></span><span class=\"token punctuation\">(</span>handler<span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\t<span class=\"token punctuation\">}</span>\n\n\t<span class=\"token keyword\">public</span> <span class=\"token return-type class-name\"><span class=\"token keyword\">void</span></span> <span class=\"token function\">RaiseEvent</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span>\n\t<span class=\"token punctuation\">{</span>\n\t\t<span class=\"token keyword\">foreach</span> <span class=\"token punctuation\">(</span><span class=\"token class-name\"><span class=\"token keyword\">var</span></span> weakHandler <span class=\"token keyword\">in</span> _handlers<span class=\"token punctuation\">)</span>\n\t\t<span class=\"token punctuation\">{</span>\n\t\t\t<span class=\"token keyword\">if</span> <span class=\"token punctuation\">(</span>weakHandler<span class=\"token punctuation\">.</span><span class=\"token function\">TryGetTarget</span><span class=\"token punctuation\">(</span><span class=\"token keyword\">out</span> <span class=\"token class-name\">EventHandler</span> handler<span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span>\n\t\t\t<span class=\"token punctuation\">{</span>\n\t\t\t\thandler<span class=\"token punctuation\">.</span><span class=\"token function\">Invoke</span><span class=\"token punctuation\">(</span><span class=\"token keyword\">this</span><span class=\"token punctuation\">,</span> EventArgs<span class=\"token punctuation\">.</span>Empty<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\t\t\t<span class=\"token punctuation\">}</span>\n\t\t<span class=\"token punctuation\">}</span>\n\t<span class=\"token punctuation\">}</span>\n<span class=\"token punctuation\">}</span></code></pre><p>In this simple example, the <code>Publisher</code> class holds a list of weak references to event handlers. When raising the event, it attempts to invoke only those handlers that are still alive. This strategy prevents dead objects from keeping themselves in memory alive indefinitely.</p><h2 id=\"the-publish%E2%80%93subscribe-pattern-and-its-synergy-with-weak-references\" tabindex=\"-1\">The Publish–Subscribe Pattern and Its Synergy with Weak References</h2><p>The publish–subscribe (pub–sub) pattern is an asynchronous messaging paradigm where publishers emit events and subscribers listen for those events. This decoupling means that the publisher does not need to know the specifics of its subscribers. Traditionally, this pattern improves scalability and maintainability, particularly in distributed systems and event-driven architectures.</p><h3 id=\"combining-weak-references-with-pub%E2%80%93sub\" tabindex=\"-1\">Combining Weak References with Pub–Sub</h3><p>By integrating weak references into a publish–subscribe system, developers can manage event subscriptions more safely. The use of weak references ensures that even if a subscriber forgets to unsubscribe, the system remains robust against memory leaks. This is especially critical in long-running applications or dynamic environments like modern web frameworks.</p><h4 id=\"example%3A-a-weak-referenced-publish%E2%80%93subscribe-system-in-c%23\" tabindex=\"-1\">Example: A Weak-Referenced Publish–Subscribe System in C#</h4><p>Consider a more structured implementation where a mediator handles event subscriptions:</p><pre class=\"language-csharp\"><code class=\"language-csharp\"><span class=\"token keyword\">public</span> <span class=\"token keyword\">interface</span> <span class=\"token class-name\">IEventSubscriber<span class=\"token punctuation\">&lt;</span>TEventArgs<span class=\"token punctuation\">></span></span>\n<span class=\"token punctuation\">{</span>\n\t<span class=\"token return-type class-name\"><span class=\"token keyword\">void</span></span> <span class=\"token function\">OnEvent</span><span class=\"token punctuation\">(</span><span class=\"token class-name\"><span class=\"token keyword\">object</span></span> sender<span class=\"token punctuation\">,</span> <span class=\"token class-name\">TEventArgs</span> e<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span>\n\n<span class=\"token keyword\">public</span> <span class=\"token keyword\">class</span> <span class=\"token class-name\">WeakEventManager<span class=\"token punctuation\">&lt;</span>TEventArgs<span class=\"token punctuation\">></span></span> <span class=\"token keyword\">where</span> <span class=\"token class-name\">TEventArgs</span> <span class=\"token punctuation\">:</span> <span class=\"token type-list\"><span class=\"token class-name\">EventArgs</span></span>\n<span class=\"token punctuation\">{</span>\n\t<span class=\"token keyword\">private</span> <span class=\"token class-name\">List<span class=\"token punctuation\">&lt;</span>WeakReference<span class=\"token punctuation\">&lt;</span>IEventSubscriber<span class=\"token punctuation\">&lt;</span>TEventArgs<span class=\"token punctuation\">></span><span class=\"token punctuation\">></span><span class=\"token punctuation\">></span></span> _subscribers\n\t\t<span class=\"token operator\">=</span> <span class=\"token keyword\">new</span> <span class=\"token constructor-invocation class-name\">List<span class=\"token punctuation\">&lt;</span>WeakReference<span class=\"token punctuation\">&lt;</span>IEventSubscriber<span class=\"token punctuation\">&lt;</span>TEventArgs<span class=\"token punctuation\">></span><span class=\"token punctuation\">></span><span class=\"token punctuation\">></span></span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\n\t<span class=\"token keyword\">public</span> <span class=\"token return-type class-name\"><span class=\"token keyword\">void</span></span> <span class=\"token function\">Subscribe</span><span class=\"token punctuation\">(</span><span class=\"token class-name\">IEventSubscriber<span class=\"token punctuation\">&lt;</span>TEventArgs<span class=\"token punctuation\">></span></span> subscriber<span class=\"token punctuation\">)</span>\n\t<span class=\"token punctuation\">{</span>\n\t\t_subscribers<span class=\"token punctuation\">.</span><span class=\"token function\">Add</span><span class=\"token punctuation\">(</span><span class=\"token keyword\">new</span> <span class=\"token constructor-invocation class-name\">WeakReference<span class=\"token punctuation\">&lt;</span>IEventSubscriber<span class=\"token punctuation\">&lt;</span>TEventArgs<span class=\"token punctuation\">></span><span class=\"token punctuation\">></span></span><span class=\"token punctuation\">(</span>subscriber<span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\t<span class=\"token punctuation\">}</span>\n\n\t<span class=\"token keyword\">public</span> <span class=\"token return-type class-name\"><span class=\"token keyword\">void</span></span> <span class=\"token function\">Unsubscribe</span><span class=\"token punctuation\">(</span><span class=\"token class-name\">IEventSubscriber<span class=\"token punctuation\">&lt;</span>TEventArgs<span class=\"token punctuation\">></span></span> subscriber<span class=\"token punctuation\">)</span>\n\t<span class=\"token punctuation\">{</span>\n\t\t_subscribers<span class=\"token punctuation\">.</span><span class=\"token function\">RemoveAll</span><span class=\"token punctuation\">(</span>wr <span class=\"token operator\">=></span>\n\t\t<span class=\"token punctuation\">{</span>\n\t\t\t<span class=\"token keyword\">if</span> <span class=\"token punctuation\">(</span>wr<span class=\"token punctuation\">.</span><span class=\"token function\">TryGetTarget</span><span class=\"token punctuation\">(</span><span class=\"token keyword\">out</span> <span class=\"token class-name\">IEventSubscriber<span class=\"token punctuation\">&lt;</span>TEventArgs<span class=\"token punctuation\">></span></span> target<span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span>\n\t\t\t<span class=\"token punctuation\">{</span>\n\t\t\t\t<span class=\"token keyword\">return</span> target<span class=\"token punctuation\">.</span><span class=\"token function\">Equals</span><span class=\"token punctuation\">(</span>subscriber<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\t\t\t<span class=\"token punctuation\">}</span>\n\t\t\t<span class=\"token keyword\">return</span> <span class=\"token boolean\">true</span><span class=\"token punctuation\">;</span> <span class=\"token comment\">// Clean up dead references</span>\n\t\t<span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\t<span class=\"token punctuation\">}</span>\n\n\t<span class=\"token keyword\">public</span> <span class=\"token return-type class-name\"><span class=\"token keyword\">void</span></span> <span class=\"token function\">RaiseEvent</span><span class=\"token punctuation\">(</span><span class=\"token class-name\"><span class=\"token keyword\">object</span></span> sender<span class=\"token punctuation\">,</span> <span class=\"token class-name\">TEventArgs</span> e<span class=\"token punctuation\">)</span>\n\t<span class=\"token punctuation\">{</span>\n\t\t_subscribers<span class=\"token punctuation\">.</span><span class=\"token function\">RemoveAll</span><span class=\"token punctuation\">(</span>wr <span class=\"token operator\">=></span> <span class=\"token operator\">!</span>wr<span class=\"token punctuation\">.</span><span class=\"token function\">TryGetTarget</span><span class=\"token punctuation\">(</span><span class=\"token keyword\">out</span> _<span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span> <span class=\"token comment\">// Cleanup</span>\n\t\t<span class=\"token keyword\">foreach</span> <span class=\"token punctuation\">(</span><span class=\"token class-name\"><span class=\"token keyword\">var</span></span> weakSubscriber <span class=\"token keyword\">in</span> _subscribers<span class=\"token punctuation\">)</span>\n\t\t<span class=\"token punctuation\">{</span>\n\t\t\t<span class=\"token keyword\">if</span> <span class=\"token punctuation\">(</span>weakSubscriber<span class=\"token punctuation\">.</span><span class=\"token function\">TryGetTarget</span><span class=\"token punctuation\">(</span><span class=\"token keyword\">out</span> <span class=\"token class-name\">IEventSubscriber<span class=\"token punctuation\">&lt;</span>TEventArgs<span class=\"token punctuation\">></span></span> subscriber<span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span>\n\t\t\t<span class=\"token punctuation\">{</span>\n\t\t\t\tsubscriber<span class=\"token punctuation\">.</span><span class=\"token function\">OnEvent</span><span class=\"token punctuation\">(</span>sender<span class=\"token punctuation\">,</span> e<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\t\t\t<span class=\"token punctuation\">}</span>\n\t\t<span class=\"token punctuation\">}</span>\n\t<span class=\"token punctuation\">}</span>\n<span class=\"token punctuation\">}</span></code></pre><p>In this code, the <code>WeakEventManager&lt;TEventArgs&gt;</code> class encapsulates the publish–subscribe pattern. It leverages weak references to avoid preventing the garbage collection of its subscribers. The cleanup logic ensures that any references to objects that have been collected are removed.</p><h2 id=\"real-world-applications%3A-byteaether.weakevent-nuget-library\" tabindex=\"-1\">Real-World Applications: ByteAether.WeakEvent NuGet Library</h2><p>The <a href=\"https://github.com/ByteAether/WeakEvent\" target=\"_blank\" rel=\"noopener noreferrer\">ByteAether.WeakEvent</a> <a href=\"https://www.nuget.org/packages/ByteAether.WeakEvent/\" target=\"_blank\" rel=\"noopener noreferrer\">NuGet library</a> provides a ready-to-use implementation of the generic weak event pattern using the publish–subscribe mechanism. It is built on the principles discussed above and is tailored for .NET developers seeking an out-of-the-box solution for managing event lifecycles.</p><p>According to its description:</p><blockquote><p>The weak event pattern is a proven approach to managing event subscriptions in .NET, as detailed in Microsoft's documentation on <a href=\"https://learn.microsoft.com/en-us/dotnet/desktop/wpf/events/weak-event-patterns?view=netdesktop-9.0\" target=\"_blank\" rel=\"noopener noreferrer\">WPF weak event patterns</a>. This design avoids memory leaks by holding event subscribers with weak references, which means that even if an object remains subscribed to an event, it can still be garbage collected when no longer in use.</p><p>Leveraging the power of .NET's weak reference mechanism, as explained in the <a href=\"https://learn.microsoft.com/en-us/dotnet/standard/garbage-collection/weak-references\" target=\"_blank\" rel=\"noopener noreferrer\">.NET garbage collection documentation</a>, the WeakEvent library ensures that event subscribers do not prevent the garbage collector from reclaiming memory. This decoupling of publishers and subscribers provides a robust solution for managing event lifecycles without the need for manual unsubscription.</p></blockquote><h3 id=\"benefits-in-blazor-and-other-modern-frameworks\" tabindex=\"-1\">Benefits in Blazor and Other Modern Frameworks</h3><p>When developing Blazor components, for example, unsubscribing from events during component disposal can be tricky. The <strong>ByteAether.WeakEvent</strong> library addresses this by ensuring that subscriptions are weak; once a component is no longer rendered, the weak reference does not prevent its memory from being reclaimed. This automatic cleanup is invaluable in preventing the accumulation of &quot;dead&quot; components that can degrade performance over time.</p><h4 id=\"example-usage-in-a-blazor-component\" tabindex=\"-1\">Example Usage in a Blazor Component</h4><pre class=\"language-csharp\"><code class=\"language-csharp\">@code <span class=\"token punctuation\">{</span>\n\t<span class=\"token punctuation\">[</span><span class=\"token attribute\"><span class=\"token class-name\">Inject</span></span><span class=\"token punctuation\">]</span>\n\t<span class=\"token keyword\">protected</span> <span class=\"token keyword\">readonly</span> <span class=\"token return-type class-name\">Publisher</span> _publisher <span class=\"token punctuation\">{</span> <span class=\"token keyword\">get</span><span class=\"token punctuation\">;</span> <span class=\"token keyword\">set</span><span class=\"token punctuation\">;</span> <span class=\"token punctuation\">}</span> <span class=\"token operator\">=</span> <span class=\"token keyword\">default</span><span class=\"token operator\">!</span><span class=\"token punctuation\">;</span>\n\n\t<span class=\"token keyword\">protected</span> <span class=\"token keyword\">override</span> <span class=\"token return-type class-name\"><span class=\"token keyword\">void</span></span> <span class=\"token function\">OnInitialized</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span>\n\t<span class=\"token punctuation\">{</span>\n\t\t<span class=\"token comment\">// Assume Publisher has a public property WeakEvent&lt;MyEventData> OnPublish</span>\n\t\t_publisher<span class=\"token punctuation\">.</span>OnPublish<span class=\"token punctuation\">.</span><span class=\"token function\">Subscribe</span><span class=\"token punctuation\">(</span>OnEvent<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\t<span class=\"token punctuation\">}</span>\n\n\t<span class=\"token keyword\">public</span> <span class=\"token return-type class-name\"><span class=\"token keyword\">void</span></span> <span class=\"token function\">OnEvent</span><span class=\"token punctuation\">(</span><span class=\"token class-name\">MyEventData</span> eventData<span class=\"token punctuation\">)</span>\n\t<span class=\"token punctuation\">{</span>\n\t\t<span class=\"token comment\">// Handle the event (e.g., update UI state)</span>\n\t\tConsole<span class=\"token punctuation\">.</span><span class=\"token function\">WriteLine</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"Event received in Blazor component.\"</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\t<span class=\"token punctuation\">}</span>\n\n\t<span class=\"token keyword\">public</span> <span class=\"token return-type class-name\"><span class=\"token keyword\">void</span></span> <span class=\"token function\">Dispose</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span>\n\t<span class=\"token punctuation\">{</span>\n\t\t<span class=\"token comment\">// No need to manually unsubscribe, the weak reference handles cleanup.</span>\n\t<span class=\"token punctuation\">}</span>\n<span class=\"token punctuation\">}</span></code></pre><p>In this Blazor example, the component subscribes to event in a <code>Publisher</code> instance, that makes use of ByteAether.WeakEvent library. Even if the component is disposed, the weak reference does not hinder the garbage collection process, eliminating one of the common pitfalls in event-driven UI development.</p><h2 id=\"broader-implications-and-cross-platform-benefits\" tabindex=\"-1\">Broader Implications and Cross-Platform Benefits</h2><p>Although our examples and discussion have been centered on C# and .NET, the challenges of managing event lifecycles and preventing memory leaks are common across all programming environments. Languages and frameworks that use event-driven paradigms—such as JavaScript, Java, and even C++—face similar issues when subscribers outlive their useful life due to lingering references.</p><h3 id=\"applying-weak-referenced-event-patterns-in-other-environments\" tabindex=\"-1\">Applying Weak-Referenced Event Patterns in Other Environments</h3><p>Many modern frameworks provide mechanisms similar to .NET's weak references. For instance:</p><ul><li><strong>JavaScript:</strong> Libraries like RxJS help manage subscriptions in a reactive programming model. Although JavaScript does not have a built-in weak reference mechanism in all runtimes, the concept of unsubscribing (or using operators like <code>takeUntil</code>) plays a similar role in managing memory.</li><li><strong>Java:</strong> The use of <code>WeakReference</code> in Java follows similar principles, allowing developers to build event systems where the publisher does not inadvertently extend the lifetime of the subscriber.</li><li><strong>C++:</strong> With careful design, smart pointers and observer patterns can mimic weak event management to avoid memory leaks.</li></ul><p>Thus, while the syntax and APIs differ, the fundamental benefit remains the same: decoupling the event subscription mechanism from the object lifecycle leads to more robust, maintainable, and memory-efficient applications.</p><h2 id=\"conclusion\" tabindex=\"-1\">Conclusion</h2><p>The weak event pattern combined with the publish–subscribe model represents a powerful approach to managing events in software engineering. By utilizing weak references, developers can mitigate common pitfalls such as memory leaks and dangling subscriptions, ensuring that components are cleaned up promptly by the garbage collector. In the .NET world, where managed memory and automatic garbage collection are key features, these patterns are especially valuable.</p><p>Libraries like <a href=\"https://github.com/ByteAether/WeakEvent\" target=\"_blank\" rel=\"noopener noreferrer\">ByteAether.WeakEvent</a> encapsulate these best practices, providing developers with a plug-and-play solution to implement weak event patterns easily. Whether you're developing desktop applications with WPF, building modern web applications with Blazor, or designing systems in other environments, understanding and leveraging weak-referenced event managers can significantly enhance application reliability and performance.</p><p>As the landscape of software development evolves—with more dynamic and component-based systems emerging—the need for robust event management becomes increasingly critical. Embracing patterns that promote decoupling and automatic resource management not only leads to cleaner code but also results in applications that are more scalable and resilient in the face of growing complexity.</p><p>By integrating these approaches into your development practices, you are better equipped to build systems that manage resources intelligently, ultimately leading to a smoother user experience and more maintainable codebases.</p>",
      "date_published": "2025-02-25T00:00:00Z"
    }
    ,
    {
      "id": "https://byteaether.github.io/2025/seamlessly-connecting-frontend-and-backend-data-in-net-applications-a-new-paradigm-for-effortless-integration/",
      "url": "https://byteaether.github.io/2025/seamlessly-connecting-frontend-and-backend-data-in-net-applications-a-new-paradigm-for-effortless-integration/",
      "title": "Seamlessly Connecting Frontend and Backend Data in .NET Applications: A New Paradigm for Effortless Integration",
      "content_html": "<p>In today's fast‐paced software development world, engineers are often forced to write repetitive integration logic to connect user interfaces - especially data grids and tables - to their backend data sources. Every project, every new feature, and every iteration seems to reintroduce the same challenges: how do we reliably filter, sort, and display data without reinventing the wheel each time? The answer lies in automating these common tasks, thereby freeing developers to focus on business logic rather than boilerplate code.</p><h2 id=\"the-problem%3A-repetitive-integration-logic\" tabindex=\"-1\">The Problem: Repetitive Integration Logic</h2><p>Consider a typical .NET application where you need to fetch, filter, and sort data from a SQL database using Entity Framework Core. In many projects, developers are repeatedly writing similar code snippets to achieve this functionality. For example:</p><pre class=\"language-csharp\"><code class=\"language-csharp\"><span class=\"token comment\">// Example: Manually applying filters and sorting on a data source</span>\n<span class=\"token keyword\">public</span> <span class=\"token return-type class-name\">IQueryable<span class=\"token punctuation\">&lt;</span>Person<span class=\"token punctuation\">></span></span> <span class=\"token function\">GetFilteredAndSortedPeople</span><span class=\"token punctuation\">(</span>\n\t<span class=\"token class-name\">ApplicationDbContext</span> dbContext<span class=\"token punctuation\">,</span>\n\t<span class=\"token class-name\"><span class=\"token keyword\">string</span></span> name<span class=\"token punctuation\">,</span>\n\t<span class=\"token class-name\"><span class=\"token keyword\">int</span><span class=\"token punctuation\">?</span></span> minAge<span class=\"token punctuation\">,</span>\n\t<span class=\"token class-name\"><span class=\"token keyword\">string</span></span> sortField\n<span class=\"token punctuation\">)</span>\n<span class=\"token punctuation\">{</span>\n\t<span class=\"token class-name\">IQueryable<span class=\"token punctuation\">&lt;</span>Person<span class=\"token punctuation\">></span></span> query <span class=\"token operator\">=</span> dbContext<span class=\"token punctuation\">.</span>People<span class=\"token punctuation\">.</span><span class=\"token function\">AsQueryable</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\n\t<span class=\"token comment\">// Repetitive filtering logic:</span>\n\t<span class=\"token keyword\">if</span> <span class=\"token punctuation\">(</span><span class=\"token operator\">!</span><span class=\"token keyword\">string</span><span class=\"token punctuation\">.</span><span class=\"token function\">IsNullOrWhiteSpace</span><span class=\"token punctuation\">(</span>name<span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span>\n\t<span class=\"token punctuation\">{</span>\n\t\tquery <span class=\"token operator\">=</span> query<span class=\"token punctuation\">.</span><span class=\"token function\">Where</span><span class=\"token punctuation\">(</span>p <span class=\"token operator\">=></span> p<span class=\"token punctuation\">.</span>Name <span class=\"token operator\">==</span> name<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\t<span class=\"token punctuation\">}</span>\n\t<span class=\"token keyword\">if</span> <span class=\"token punctuation\">(</span>minAge<span class=\"token punctuation\">.</span>HasValue<span class=\"token punctuation\">)</span>\n\t<span class=\"token punctuation\">{</span>\n\t\tquery <span class=\"token operator\">=</span> query<span class=\"token punctuation\">.</span><span class=\"token function\">Where</span><span class=\"token punctuation\">(</span>p <span class=\"token operator\">=></span> p<span class=\"token punctuation\">.</span>Age <span class=\"token operator\">>=</span> minAge<span class=\"token punctuation\">.</span>Value<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\t<span class=\"token punctuation\">}</span>\n\n\t<span class=\"token comment\">// Repetitive sorting logic:</span>\n\t<span class=\"token keyword\">if</span> <span class=\"token punctuation\">(</span>sortField <span class=\"token operator\">==</span> <span class=\"token string\">\"Name\"</span><span class=\"token punctuation\">)</span>\n\t<span class=\"token punctuation\">{</span>\n\t\tquery <span class=\"token operator\">=</span> query<span class=\"token punctuation\">.</span><span class=\"token function\">OrderBy</span><span class=\"token punctuation\">(</span>p <span class=\"token operator\">=></span> p<span class=\"token punctuation\">.</span>Name<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\t<span class=\"token punctuation\">}</span>\n\t<span class=\"token keyword\">else</span> <span class=\"token keyword\">if</span> <span class=\"token punctuation\">(</span>sortField <span class=\"token operator\">==</span> <span class=\"token string\">\"Age\"</span><span class=\"token punctuation\">)</span>\n\t<span class=\"token punctuation\">{</span>\n\t\tquery <span class=\"token operator\">=</span> query<span class=\"token punctuation\">.</span><span class=\"token function\">OrderByDescending</span><span class=\"token punctuation\">(</span>p <span class=\"token operator\">=></span> p<span class=\"token punctuation\">.</span>Age<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\t<span class=\"token punctuation\">}</span>\n\n\t<span class=\"token keyword\">return</span> query<span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span></code></pre><p>Now, imagine this pattern repeated across multiple controllers, services, and even projects. Each time you need to tweak a filter or adjust a sort order, you are forced to manually update these code blocks - often duplicating logic and introducing room for inconsistencies or bugs.</p><p>Another example might involve converting query parameters from the UI into LINQ expressions:</p><pre class=\"language-csharp\"><code class=\"language-csharp\"><span class=\"token comment\">// Example: Converting UI query parameters into LINQ filter expressions</span>\n<span class=\"token keyword\">public</span> <span class=\"token return-type class-name\">IQueryable<span class=\"token punctuation\">&lt;</span>Product<span class=\"token punctuation\">></span></span> <span class=\"token function\">FilterProducts</span><span class=\"token punctuation\">(</span>\n\t<span class=\"token class-name\">IQueryable<span class=\"token punctuation\">&lt;</span>Product<span class=\"token punctuation\">></span></span> query<span class=\"token punctuation\">,</span>\n\t<span class=\"token class-name\">Dictionary<span class=\"token punctuation\">&lt;</span><span class=\"token keyword\">string</span><span class=\"token punctuation\">,</span> <span class=\"token keyword\">string</span><span class=\"token punctuation\">></span></span> filters\n<span class=\"token punctuation\">)</span>\n<span class=\"token punctuation\">{</span>\n\t<span class=\"token keyword\">foreach</span> <span class=\"token punctuation\">(</span><span class=\"token class-name\"><span class=\"token keyword\">var</span></span> filter <span class=\"token keyword\">in</span> filters<span class=\"token punctuation\">)</span>\n\t<span class=\"token punctuation\">{</span>\n\t\t<span class=\"token keyword\">switch</span> <span class=\"token punctuation\">(</span>filter<span class=\"token punctuation\">.</span>Key<span class=\"token punctuation\">.</span><span class=\"token function\">ToLower</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span>\n\t\t<span class=\"token punctuation\">{</span>\n\t\t\t<span class=\"token keyword\">case</span> <span class=\"token string\">\"category\"</span><span class=\"token punctuation\">:</span>\n\t\t\t\tquery <span class=\"token operator\">=</span> query<span class=\"token punctuation\">.</span><span class=\"token function\">Where</span><span class=\"token punctuation\">(</span>p <span class=\"token operator\">=></span> p<span class=\"token punctuation\">.</span>Category <span class=\"token operator\">==</span> filter<span class=\"token punctuation\">.</span>Value<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\t\t\t\t<span class=\"token keyword\">break</span><span class=\"token punctuation\">;</span>\n\t\t\t<span class=\"token keyword\">case</span> <span class=\"token string\">\"price\"</span><span class=\"token punctuation\">:</span>\n\t\t\t\t<span class=\"token keyword\">if</span> <span class=\"token punctuation\">(</span><span class=\"token keyword\">decimal</span><span class=\"token punctuation\">.</span><span class=\"token function\">TryParse</span><span class=\"token punctuation\">(</span>filter<span class=\"token punctuation\">.</span>Value<span class=\"token punctuation\">,</span> <span class=\"token keyword\">out</span> <span class=\"token class-name\"><span class=\"token keyword\">var</span></span> price<span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span>\n\t\t\t\t<span class=\"token punctuation\">{</span>\n\t\t\t\t\tquery <span class=\"token operator\">=</span> query<span class=\"token punctuation\">.</span><span class=\"token function\">Where</span><span class=\"token punctuation\">(</span>p <span class=\"token operator\">=></span> p<span class=\"token punctuation\">.</span>Price <span class=\"token operator\">>=</span> price<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\t\t\t\t<span class=\"token punctuation\">}</span>\n\t\t\t\t<span class=\"token keyword\">break</span><span class=\"token punctuation\">;</span>\n\t\t\t<span class=\"token keyword\">default</span><span class=\"token punctuation\">:</span>\n\t\t\t\t<span class=\"token keyword\">break</span><span class=\"token punctuation\">;</span>\n\t\t<span class=\"token punctuation\">}</span>\n\t<span class=\"token punctuation\">}</span>\n\n\t<span class=\"token keyword\">return</span> query<span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span></code></pre><h3 id=\"problems-with-this-approach%3A\" tabindex=\"-1\">Problems with this approach:</h3><ul><li><strong>Wastes valuable time</strong>: Developers must repeatedly implement similar logic across different parts of an application.</li><li><strong>Increases error potential</strong>: Manual adjustments lead to inconsistencies and bugs.</li><li><strong>Reduces maintainability</strong>: Updates must be applied in multiple places, making maintenance a challenge.</li></ul><p>Clearly, there is a need for a generic, reusable solution that abstracts away this repetitive integration logic.</p><h2 id=\"identifying-the-requirements-for-a-generic-solution\" tabindex=\"-1\">Identifying the Requirements for a Generic Solution</h2><p>To create a generic solution for handling data integration, certain core requirements must be met:</p><h3 id=\"1.-serializability\" tabindex=\"-1\">1. Serializability</h3><p>For a solution to work seamlessly between the client and server, its filter and order definitions must be serializable. This allows easy transmission over HTTP without losing state.</p><h3 id=\"2.-integration-with-iqueryable-and-ef-core\" tabindex=\"-1\">2. Integration with IQueryable and EF Core</h3><p>The solution must work directly with <code>IQueryable</code>, ensuring that filtering and sorting logic is applied at the database level, improving performance.</p><h3 id=\"3.-flexible-filter-definitions\" tabindex=\"-1\">3. Flexible Filter Definitions</h3><p>A robust solution should support a variety of filtering operators such as:</p><ul><li><strong>Equals (<code>=</code>) / Not Equals (<code>!=</code>)</strong></li><li><strong>Greater Than (<code>&gt;</code>) / Less Than (<code>&lt;</code>)</strong></li><li><strong>Contains (<code>=*</code>)</strong></li></ul><p>... and more.</p><h3 id=\"4.-support-for-overrides\" tabindex=\"-1\">4. Support for Overrides</h3><p>Developers should be able to specify overrides for sorting and filtering logic to handle cases where default behavior isn't sufficient. For example, sorting should support custom overrides, like sorting <code>Firstname</code> as <code>[Firstname] [Lastname]</code> to ensure consistent ordering.</p><h2 id=\"a-modern-approach-to-automating-data-query-integration\" tabindex=\"-1\">A Modern Approach to Automating Data Query Integration</h2><p>An innovative solution was born: <strong><a href=\"https://github.com/ByteAether/QueryLink/\" target=\"_blank\" rel=\"noopener noreferrer\">QueryLink</a></strong>. This library encapsulates all the above requirements into a single, easy-to-use package.</p><p><strong>Key Features:</strong></p><ol><li><strong>Filter and Order Definitions</strong>: Define filters and sorting orders without writing complex LINQ expressions.</li><li><strong>Expression-based Overrides</strong>: Customize filtering or sorting logic using lambda expressions with type safety.</li><li><strong>Query String Conversion</strong>: Convert definitions to query strings for seamless transmission over <code>GET</code> parameters.</li><li><strong>Direct IQueryable Integration</strong>: Ensures efficient query execution at the database level.</li></ol><h2 id=\"getting-started%3A-hands-on-code-examples\" tabindex=\"-1\">Getting Started: Hands-On Code Examples</h2><h3 id=\"defining-filters-and-sorting-orders\" tabindex=\"-1\">Defining Filters and Sorting Orders</h3><pre class=\"language-csharp\"><code class=\"language-csharp\"><span class=\"token class-name\"><span class=\"token keyword\">var</span></span> definitions <span class=\"token operator\">=</span> <span class=\"token keyword\">new</span> <span class=\"token constructor-invocation class-name\">Definitions</span>\n<span class=\"token punctuation\">{</span>\n\tFilters <span class=\"token operator\">=</span>\n\t<span class=\"token punctuation\">[</span>\n\t\t<span class=\"token keyword\">new</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"Name\"</span><span class=\"token punctuation\">,</span> FilterOperator<span class=\"token punctuation\">.</span>Eq<span class=\"token punctuation\">,</span> <span class=\"token string\">\"John\"</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">,</span>\n\t\t<span class=\"token keyword\">new</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"Age\"</span><span class=\"token punctuation\">,</span> FilterOperator<span class=\"token punctuation\">.</span>Gt<span class=\"token punctuation\">,</span> <span class=\"token number\">30</span><span class=\"token punctuation\">)</span>\n\t<span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span>\n\tOrders <span class=\"token operator\">=</span>\n\t<span class=\"token punctuation\">[</span>\n\t\t<span class=\"token keyword\">new</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"Name\"</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">,</span>\n\t\t<span class=\"token keyword\">new</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"Age\"</span><span class=\"token punctuation\">,</span> <span class=\"token named-parameter punctuation\">IsReversed</span><span class=\"token punctuation\">:</span> <span class=\"token boolean\">true</span><span class=\"token punctuation\">)</span>\n\t<span class=\"token punctuation\">]</span>\n<span class=\"token punctuation\">}</span><span class=\"token punctuation\">;</span></code></pre><h3 id=\"customizing-with-overrides-(optional)\" tabindex=\"-1\">Customizing with Overrides (Optional)</h3><pre class=\"language-csharp\"><code class=\"language-csharp\"><span class=\"token class-name\"><span class=\"token keyword\">var</span></span> overrides <span class=\"token operator\">=</span> <span class=\"token keyword\">new</span> <span class=\"token constructor-invocation class-name\">Overrides<span class=\"token punctuation\">&lt;</span>Person<span class=\"token punctuation\">></span></span>\n<span class=\"token punctuation\">{</span>\n\tFilter <span class=\"token operator\">=</span>\n\t<span class=\"token punctuation\">[</span>\n\t\t<span class=\"token keyword\">new</span><span class=\"token punctuation\">(</span>p <span class=\"token operator\">=></span> p<span class=\"token punctuation\">.</span>Name<span class=\"token punctuation\">,</span> p <span class=\"token operator\">=></span> p<span class=\"token punctuation\">.</span>FullName<span class=\"token punctuation\">)</span>\n\t<span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span>\n\tOrder <span class=\"token operator\">=</span>\n\t<span class=\"token punctuation\">[</span>\n\t\t<span class=\"token keyword\">new</span><span class=\"token punctuation\">(</span>p <span class=\"token operator\">=></span> p<span class=\"token punctuation\">.</span>Name<span class=\"token punctuation\">,</span> p <span class=\"token operator\">=></span> p<span class=\"token punctuation\">.</span>FullName<span class=\"token punctuation\">)</span>\n\t<span class=\"token punctuation\">]</span>\n<span class=\"token punctuation\">}</span><span class=\"token punctuation\">;</span></code></pre><h3 id=\"applying-definitions-to-an-iqueryable-source\" tabindex=\"-1\">Applying Definitions to an IQueryable Source</h3><pre class=\"language-csharp\"><code class=\"language-csharp\"><span class=\"token class-name\">IQueryable<span class=\"token punctuation\">&lt;</span>Person<span class=\"token punctuation\">></span></span> query <span class=\"token operator\">=</span> dbContext<span class=\"token punctuation\">.</span>People<span class=\"token punctuation\">.</span><span class=\"token function\">AsQueryable</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\nquery <span class=\"token operator\">=</span> query<span class=\"token punctuation\">.</span><span class=\"token function\">Apply</span><span class=\"token punctuation\">(</span>definitions<span class=\"token punctuation\">,</span> overrides<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span></code></pre><h2 id=\"embracing-the-new-way-of-integration-logic\" tabindex=\"-1\">Embracing the New Way of Integration Logic</h2><p>With QueryLink:</p><ul><li><strong>Eliminate Repetitiveness</strong>: No more writing the same boilerplate code repeatedly.</li><li><strong>Improve Code Clarity</strong>: Declarative definitions make code easier to read and maintain.</li><li><strong>Enhance Consistency</strong>: Apply the same filtering and sorting logic across the application.</li><li><strong>Speed Up Development</strong>: Focus on business logic instead of integration logic.</li></ul><h2 id=\"why-querylink-is-a-must-have-in-every-.net-project\" tabindex=\"-1\">Why QueryLink Is a Must-Have in Every .NET Project</h2><p>QueryLink transforms UI-to-database integration by:</p><ul><li><strong>Streamlining Development</strong>: Reducing the need for repetitive query logic.</li><li><strong>Ensuring Consistency</strong>: Standardizing filtering and sorting mechanisms.</li><li><strong>Enhancing Maintainability</strong>: Centralizing data integration logic.</li></ul><p>Given these advantages, <strong>QueryLink is an essential library for every full-stack .NET application</strong>. By integrating this tool into your workflow, you empower your team to build more efficient and maintainable software.</p><p><em>Embrace the future of data integration with QueryLink.</em></p><p><a href=\"https://github.com/ByteAether/QueryLink/\" target=\"_blank\" rel=\"noopener noreferrer\">Check out the QueryLink repository on GitHub</a>.</p>",
      "date_published": "2025-02-18T00:00:00Z"
    }
    ,
    {
      "id": "https://byteaether.github.io/2025/ulids-as-the-default-choice-for-modern-systems-lessons-from-shopifys-payment-infrastructure/",
      "url": "https://byteaether.github.io/2025/ulids-as-the-default-choice-for-modern-systems-lessons-from-shopifys-payment-infrastructure/",
      "title": "ULIDs as the Default Choice for Modern Systems: Lessons from Shopify&#39;s Payment Infrastructure",
      "content_html": "<p>In distributed systems, the choice of identifier is rarely a neutral decision. It impacts everything from database performance to operational debuggability. For years, UUIDs (Universally Unique Identifiers) were the default solution for generating unique keys, but their limitations become glaring in high-scale environments. Shopify's engineering team encountered these challenges firsthand while rebuilding their payment systems - a project where milliseconds and reliability mattered. Their solution? <a href=\"https://github.com/ulid/spec\" target=\"_blank\" rel=\"noopener noreferrer\">ULIDs (Universally Unique Lexicographically Sortable Identifiers)</a>. This article explores why ULIDs are not just an alternative to UUIDs but a superior default choice for most applications, drawing on Shopify's experience to illustrate their transformative potential.</p><h2 id=\"what-are-ulids%2C-and-why-do-they-matter%3F\" tabindex=\"-1\">What Are ULIDs, and Why Do They Matter?</h2><p>A ULID is a 128-bit identifier designed to combine uniqueness with inherent sortability. Its structure is simple but powerful:</p><ul><li>The first 48 bits encode a timestamp, representing milliseconds since the Unix epoch.</li><li>The remaining 80 bits are cryptographically secure random data, ensuring global uniqueness.</li></ul><p>For example, the ULID <code>01JJP9VSVRWSMP4QEJTYYFSJE8</code> starts with a timestamp (<code>01JJP9VSVR</code>) that corresponds to a precise moment in time, followed by a random suffix (<code>WSMP4QEJTYYFSJE8</code>). This design allows ULIDs to be sorted lexicographically, meaning they retain the chronological order of their creation. Unlike UUIDs, which are fundamentally random, ULIDs embed meaningful metadata (the timestamp) while preserving uniqueness.</p><h3 id=\"the-problem-with-uuids-in-real-world-systems\" tabindex=\"-1\">The Problem with UUIDs in Real-World Systems</h3><p>Shopify's payment systems initially relied on UUIDv4 for idempotency keys—unique identifiers that prevent duplicate processing of payment requests. While UUIDs ensured uniqueness, their randomness created two critical issues:</p><ol><li><strong>Database Performance Degradation</strong>: Databases like MySQL use B-tree indexes, which optimize for sequential writes. Random UUIDs force the database to insert entries at arbitrary positions in the index, leading to frequent page splits. These splits fragment the index, increasing write latency and disk I/O. At Shopify's scale, where millions of transactions occur daily, this fragmentation caused measurable slowdowns.</li><li><strong>Operational Complexity</strong>: UUIDs lack embedded metadata. To trace the timing of a transaction, engineers had to join tables or query additional timestamp columns. This added complexity to debugging and auditing, especially during incidents requiring rapid root-cause analysis.</li></ol><p>ULIDs addressed both problems. By making identifiers sortable, they eliminated the database fragmentation caused by UUIDs. The embedded timestamp also provided immediate context for debugging, reducing reliance on secondary metadata.</p><h2 id=\"shopify's-journey%3A-how-ulids-transformed-their-payment-systems\" tabindex=\"-1\">Shopify's Journey: How ULIDs Transformed Their Payment Systems</h2><h3 id=\"the-role-of-idempotency-keys\" tabindex=\"-1\">The Role of Idempotency Keys</h3><p>In payment processing, idempotency is critical. If a buyer's network connection drops during checkout, the client must retry the payment without risking a double charge. Idempotency keys solve this: the client sends the same key with each retry, and the server returns the original result instead of reprocessing the request.</p><p>Initially, Shopify used UUIDv4 for these keys. However, as transaction volume grew, the limitations of random UUIDs became untenable.</p><h3 id=\"the-switch-to-ulids\" tabindex=\"-1\">The Switch to ULIDs</h3><p>Shopify replaced UUIDv4 with ULIDs for idempotency keys, achieving three key improvements:</p><ol><li><strong>50% Faster Database Inserts</strong>: By ensuring identifiers were chronologically ordered, ULIDs allowed MySQL to append new records sequentially. This aligned with the natural behavior of B-tree indexes, reducing page splits and write latency. In high-throughput systems, this optimization halved the time required for INSERT operations.</li><li><strong>Simplified Debugging</strong>: The timestamp within ULIDs eliminated the need for separate timestamp columns. Engineers could instantly determine when a transaction occurred by parsing the ULID, accelerating incident response. For example, filtering logs by ULID prefixes narrowed results to specific time windows without complex queries.</li><li><strong>Reduced Storage Overhead</strong>: With ULIDs, Shopify no longer needed to maintain separate timestamp columns for sorting. This streamlined their schema and reduced storage costs.</li></ol><h3 id=\"technical-implementation-insights\" tabindex=\"-1\">Technical Implementation Insights</h3><p>Shopify's implementation highlights practical considerations for adopting ULIDs:</p><ul><li><strong>Client-Side Generation</strong>: ULIDs were generated at the client level to avoid centralized bottlenecks. This distributed approach aligned with Shopify's resilience goals, ensuring no single point of failure in key generation.</li><li><strong>Binary Storage</strong>: Storing ULIDs as binary data (rather than strings) minimized storage footprint while retaining sortability.</li><li><strong>Backward Compatibility</strong>: Existing UUIDs were left untouched in low-priority systems, allowing incremental migration without disrupting legacy workflows.</li></ul><h2 id=\"why-ulids-are-almost-always-the-better-default\" tabindex=\"-1\">Why ULIDs Are Almost Always the Better Default</h2><h3 id=\"performance-and-scalability\" tabindex=\"-1\">Performance and Scalability</h3><p>In distributed systems, write performance is often a bottleneck. ULIDs eliminate the overhead of random writes, making them ideal for high-throughput applications like payment processing, event logging, or real-time analytics. Sequential writes reduce disk I/O and index maintenance, which is critical at scale.</p><h3 id=\"built-in-observability\" tabindex=\"-1\">Built-In Observability</h3><p>ULIDs embed a creation timestamp, effectively acting as free metadata. This is invaluable for debugging:</p><ul><li>Engineers can correlate logs across services using the ULID's timestamp, even in systems without centralized tracing.</li><li>Auditing becomes simpler, as the order of events is implicit in the identifier itself.</li></ul><h3 id=\"future-proof-design\" tabindex=\"-1\">Future-Proof Design</h3><p>ULIDs are encoding-agnostic. While often represented in Base32 (which is URL-safe and avoids UUID's hexadecimal ambiguity), they can be stored as binary or other formats. This flexibility makes ULIDs adaptable to diverse use cases, from API request IDs to distributed trace identifiers.</p><h3 id=\"collision-resistance%3A-sufficient-for-most-cases\" tabindex=\"-1\">Collision Resistance: Sufficient for Most Cases</h3><p>While UUIDv4 offers 122 bits of randomness (compared to ULID's 80 bits), ULID's collision probability remains negligible for nearly all applications. At 80 bits, the chance of collision is roughly 1 in 1.2×10^24 — a risk so low that even systems processing billions of daily transactions would take millennia to encounter a duplicate.</p><h2 id=\"when-uuids-might-still-be-appropriate\" tabindex=\"-1\">When UUIDs Might Still Be Appropriate</h2><p>UUIDs retain niche utility in scenarios where hiding timestamps is a requirement, such as:</p><ul><li><strong>Anonymous Identifiers</strong>: Systems that anonymize user data might avoid ULIDs to prevent exposing activity timelines.</li><li><strong>Legacy Systems</strong>: Migrating entrenched UUID-based systems may not justify the effort if performance isn't a bottleneck.</li></ul><p>However, these cases are exceptions. For the vast majority of applications — payment systems, IoT event streams, user session tracking — ULIDs offer a strict superset of UUID benefits.</p><h2 id=\"adopting-ulids%3A-best-practices\" tabindex=\"-1\">Adopting ULIDs: Best Practices</h2><p>Shopify's experience provides a blueprint for successful ULID adoption:</p><ol><li><strong>Prioritize High-Impact Systems</strong>: Begin with critical components where performance gains matter most (e.g., payment idempotency keys).</li><li><strong>Leverage Database Optimizations</strong>: Use clustered indexes (e.g., InnoDB's PRIMARY KEY) to fully benefit from sequential writes.</li><li><strong>Educate Teams</strong>: Ensure engineers understand how to extract timestamps from ULIDs and leverage their sortability in queries.</li></ol><h2 id=\"conclusion%3A-ulids-as-the-new-standard\" tabindex=\"-1\">Conclusion: ULIDs as the New Standard</h2><p>Shopify's success with ULIDs underscores a broader truth: in modern system design, uniqueness alone is insufficient. Identifiers must also enable performance, observability, and scalability. ULIDs deliver on all fronts, making them the default choice for engineers building resilient systems.</p><p>UUIDs served a vital role in the past, but their randomness is a relic of an era before hyperscale systems. ULIDs, with their blend of sortability and uniqueness, address the demands of today's applications. As Shopify demonstrated, the benefits are tangible—reduced latency, streamlined debugging, and simpler schemas.</p><p>Unless a system explicitly requires timestamp obfuscation or must maintain legacy compatibility, ULIDs should be the default. They represent an evolution in identifier design, one that aligns with the realities of distributed, high-scale computing. The question isn't whether to adopt ULIDs—it's how quickly you can migrate.</p><hr/><p><em>For a deeper dive into Shopify's approach, read their original article: <a href=\"https://shopify.engineering/building-resilient-payment-systems\" target=\"_blank\" rel=\"noopener noreferrer\">10 Tips for Building Resilient Payment Systems</a>.</em></p>",
      "date_published": "2025-02-11T00:00:00Z"
    }
    ,
    {
      "id": "https://byteaether.github.io/2025/uuid-vs-ulid-vs-integer-ids-a-technical-guide-for-modern-systems/",
      "url": "https://byteaether.github.io/2025/uuid-vs-ulid-vs-integer-ids-a-technical-guide-for-modern-systems/",
      "title": "UUID vs ULID vs Integer IDs: A Technical Guide for Modern Systems",
      "content_html": "<p>Unique identifiers are critical components in software systems, serving as the foundation for data management, distributed architectures, and secure API design. While UUIDs (specifically UUIDv4) and integer IDs have been widely adopted, <a href=\"https://github.com/ulid/spec\" target=\"_blank\" rel=\"noopener noreferrer\">ULIDs (Universally Unique Lexicographically Sortable Identifiers)</a> are increasingly recognized as a superior choice for modern applications. This article explores the technical distinctions between these identifiers, focusing on performance in .NET ecosystems and the database-level implications of their structural differences.</p><h2 id=\"understanding-the-identifier-types\" tabindex=\"-1\">Understanding the Identifier Types</h2><h3 id=\"integer-ids\" tabindex=\"-1\">Integer IDs</h3><p>Integer IDs are sequential numeric values typically managed by databases through auto-increment mechanisms. Their simplicity and minimal storage requirements (4 bytes for <code>INT</code>, 8 bytes for <code>BIGINT</code>) make them straightforward to implement and efficient for indexing. However, their predictability exposes systems to security risks such as enumeration attacks (e.g., <code>/users/123</code>). Additionally, their reliance on centralized generation makes them unsuitable for distributed systems where coordination between nodes is impractical.</p><h3 id=\"uuids\" tabindex=\"-1\">UUIDs</h3><p>UUIDs are 128-bit identifiers designed to guarantee global uniqueness through randomness. The UUIDv4 variant, which uses 122 bits of random data, is the most common implementation. While UUIDs excel at collision resistance, their lack of inherent sortability leads to significant database performance issues, particularly in write-heavy systems. The random distribution of UUIDv4 values causes index fragmentation, increasing I/O overhead and reducing cache efficiency.</p><h3 id=\"ulids\" tabindex=\"-1\">ULIDs</h3><p>ULIDs are 128-bit identifiers composed of a 48-bit timestamp (millisecond precision) and 80 bits of cryptographically secure randomness. Encoded as a 26-character base32 string, ULIDs are lexicographically sortable, compact, and URL-safe. The timestamp prefix ensures that new records are inserted in chronological order, aligning with database indexing patterns to minimize fragmentation.</p><h2 id=\"performance-in-.net-applications\" tabindex=\"-1\">Performance in .NET Applications</h2><h3 id=\"ulid-generation-and-monotonicity\" tabindex=\"-1\">ULID Generation and Monotonicity</h3><p>In .NET, the <strong><a href=\"https://github.com/ByteAether/Ulid\" target=\"_blank\" rel=\"noopener noreferrer\">ByteAether.Ulid</a></strong> library provides a high-performance implementation of ULID generation. Benchmarks published in its GitHub repository demonstrate that it generates ULIDs with zero heap allocations, leveraging stack-based operations and optimized bitwise manipulation. A key feature of ULIDs is <strong>monotonicity</strong>, which ensures that identifiers generated within the same millisecond increment sequentially. This guarantees that data inserted in a logical order (e.g., batch processing workflows) remains ordered in the database. For example, in a distributed logging system, monotonicity ensures log entries from the same source retain their sequence even if generated microseconds apart. ByteAether.Ulid further enforces this by incrementing the timestamp when the random component overflows, avoiding errors during generation while preserving order.</p><h3 id=\"why-uuids-fall-short\" tabindex=\"-1\">Why UUIDs Fall Short</h3><p>While <code>Guid.NewGuid()</code> in .NET is fast, UUIDs lack structural ordering. This forces databases to insert records at random positions within indexes, leading to frequent page splits and fragmentation. ULIDs, by contrast, align insertion patterns with the natural ordering of database indexes, reducing overhead and improving throughput.</p><h2 id=\"database-performance%3A-index-fragmentation-explained\" tabindex=\"-1\">Database Performance: Index Fragmentation Explained</h2><h3 id=\"how-databases-organize-data\" tabindex=\"-1\">How Databases Organize Data</h3><p>Databases store records in fixed-size blocks called <strong>pages</strong> (e.g., 8kB in SQL Server, 16kB in MySQL). These pages are managed within <strong>B+ tree</strong> structures, where leaf nodes store actual data rows, and internal nodes route queries using key values. Sequential insertion ensures new rows are added to the end of the leaf node chain, minimizing disk seeks and memory usage.</p><h3 id=\"the-impact-of-random-identifiers\" tabindex=\"-1\">The Impact of Random Identifiers</h3><p>When identifiers are random (e.g., UUIDv4), new rows are inserted at arbitrary positions within the B+ tree. This forces databases to split existing pages to accommodate entries, a process known as <strong>page splitting</strong>. For example, inserting a record with UUID <code>f47ac10b-...</code> into a page already containing <code>a5670e02-...</code> may split the page into two, with half the rows moved to a new location.</p><p>Page splits have three major consequences:</p><ol><li><strong>Increased I/O Overhead</strong>: Fragmented pages require more disk reads to retrieve logically contiguous data.</li><li><strong>Memory Pressure</strong>: More pages are loaded into the buffer pool, reducing the cache's effectiveness.</li><li><strong>Write Amplification</strong>: Splits generate additional transaction log entries, slowing bulk inserts and increasing storage costs.</li></ol><h3 id=\"ulid%E2%80%99s-structural-advantage\" tabindex=\"-1\">ULID’s Structural Advantage</h3><p>ULIDs embed a timestamp in their high-order bits, ensuring that new records are inserted sequentially. This aligns with the B+ tree’s design, reducing page splits compared to UUIDv4.</p><h2 id=\"implementing-ulids-in-.net-with-byteaether.ulid\" tabindex=\"-1\">Implementing ULIDs in .NET with ByteAether.Ulid</h2><h3 id=\"integration-with-database-systems\" tabindex=\"-1\">Integration with Database Systems</h3><p>The <strong>ByteAether.Ulid</strong> library simplifies storing ULIDs as binary data, minimizing storage overhead and accelerating queries. Below are examples of integrating ULIDs with popular .NET data access frameworks:</p><h4 id=\"entity-framework-core-configuration\" tabindex=\"-1\">Entity Framework Core Configuration</h4><pre class=\"language-csharp\"><code class=\"language-csharp\"><span class=\"token keyword\">public</span> <span class=\"token keyword\">class</span> <span class=\"token class-name\">Order</span>\n<span class=\"token punctuation\">{</span>\n\t<span class=\"token keyword\">public</span> <span class=\"token return-type class-name\">Ulid</span> Id <span class=\"token punctuation\">{</span> <span class=\"token keyword\">get</span><span class=\"token punctuation\">;</span> <span class=\"token keyword\">set</span><span class=\"token punctuation\">;</span> <span class=\"token punctuation\">}</span> <span class=\"token comment\">// Stored as BINARY(16)</span>\n\t<span class=\"token keyword\">public</span> <span class=\"token return-type class-name\"><span class=\"token keyword\">string</span></span> Product <span class=\"token punctuation\">{</span> <span class=\"token keyword\">get</span><span class=\"token punctuation\">;</span> <span class=\"token keyword\">set</span><span class=\"token punctuation\">;</span> <span class=\"token punctuation\">}</span>\n<span class=\"token punctuation\">}</span>\n\n<span class=\"token keyword\">public</span> <span class=\"token keyword\">class</span> <span class=\"token class-name\">UlidToBytesConverter</span> <span class=\"token punctuation\">:</span> <span class=\"token type-list\"><span class=\"token class-name\">ValueConverter<span class=\"token punctuation\">&lt;</span>Ulid<span class=\"token punctuation\">,</span> <span class=\"token keyword\">byte</span><span class=\"token punctuation\">[</span><span class=\"token punctuation\">]</span><span class=\"token punctuation\">></span></span></span>\n<span class=\"token punctuation\">{</span>\n\t<span class=\"token keyword\">private</span> <span class=\"token keyword\">static</span> <span class=\"token keyword\">readonly</span> <span class=\"token class-name\">ConverterMappingHints</span> DefaultHints <span class=\"token operator\">=</span> <span class=\"token keyword\">new</span><span class=\"token punctuation\">(</span><span class=\"token named-parameter punctuation\">size</span><span class=\"token punctuation\">:</span> <span class=\"token number\">16</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\n\t<span class=\"token keyword\">public</span> <span class=\"token function\">UlidToBytesConverter</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">:</span> <span class=\"token keyword\">this</span><span class=\"token punctuation\">(</span>defaultHints<span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span> <span class=\"token punctuation\">}</span>\n\n\t<span class=\"token keyword\">public</span> <span class=\"token function\">UlidToBytesConverter</span><span class=\"token punctuation\">(</span><span class=\"token class-name\">ConverterMappingHints<span class=\"token punctuation\">?</span></span> mappingHints <span class=\"token operator\">=</span> <span class=\"token keyword\">null</span><span class=\"token punctuation\">)</span>\n\t\t<span class=\"token punctuation\">:</span> <span class=\"token keyword\">base</span><span class=\"token punctuation\">(</span>\n\t\t\t<span class=\"token named-parameter punctuation\">convertToProviderExpression</span><span class=\"token punctuation\">:</span> x <span class=\"token operator\">=></span> x<span class=\"token punctuation\">.</span><span class=\"token function\">ToByteArray</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">,</span>\n\t\t\t<span class=\"token named-parameter punctuation\">convertFromProviderExpression</span><span class=\"token punctuation\">:</span> x <span class=\"token operator\">=></span> Ulid<span class=\"token punctuation\">.</span><span class=\"token function\">New</span><span class=\"token punctuation\">(</span>x<span class=\"token punctuation\">)</span><span class=\"token punctuation\">,</span>\n\t\t\t<span class=\"token named-parameter punctuation\">mappingHints</span><span class=\"token punctuation\">:</span> defaultHints<span class=\"token punctuation\">.</span><span class=\"token function\">With</span><span class=\"token punctuation\">(</span>mappingHints<span class=\"token punctuation\">)</span>\n\t\t<span class=\"token punctuation\">)</span>\n\t<span class=\"token punctuation\">{</span> <span class=\"token punctuation\">}</span>\n<span class=\"token punctuation\">}</span>\n\n<span class=\"token comment\">// In DbContext configuration</span>\n<span class=\"token keyword\">protected</span> <span class=\"token keyword\">override</span> <span class=\"token return-type class-name\"><span class=\"token keyword\">void</span></span> <span class=\"token function\">OnModelCreating</span><span class=\"token punctuation\">(</span><span class=\"token class-name\">ModelBuilder</span> modelBuilder<span class=\"token punctuation\">)</span>\n<span class=\"token punctuation\">{</span>\n\t<span class=\"token comment\">// ...</span>\n\tconfigurationBuilder\n\t\t<span class=\"token punctuation\">.</span><span class=\"token generic-method\"><span class=\"token function\">Properties</span><span class=\"token generic class-name\"><span class=\"token punctuation\">&lt;</span>Ulid<span class=\"token punctuation\">></span></span></span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span>\n\t\t<span class=\"token punctuation\">.</span><span class=\"token generic-method\"><span class=\"token function\">HaveConversion</span><span class=\"token generic class-name\"><span class=\"token punctuation\">&lt;</span>UlidToBytesConverter<span class=\"token punctuation\">></span></span></span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\t<span class=\"token comment\">// ...</span>\n<span class=\"token punctuation\">}</span></code></pre><h4 id=\"dapper-type-handler\" tabindex=\"-1\">Dapper Type Handler</h4><pre class=\"language-csharp\"><code class=\"language-csharp\"><span class=\"token keyword\">public</span> <span class=\"token keyword\">class</span> <span class=\"token class-name\">UlidTypeHandler</span> <span class=\"token punctuation\">:</span> <span class=\"token type-list\"><span class=\"token class-name\">SqlMapper<span class=\"token punctuation\">.</span>TypeHandler<span class=\"token punctuation\">&lt;</span>Ulid<span class=\"token punctuation\">></span></span></span>\n<span class=\"token punctuation\">{</span>\n\t<span class=\"token keyword\">public</span> <span class=\"token keyword\">override</span> <span class=\"token return-type class-name\"><span class=\"token keyword\">void</span></span> <span class=\"token function\">SetValue</span><span class=\"token punctuation\">(</span><span class=\"token class-name\">IDbDataParameter</span> parameter<span class=\"token punctuation\">,</span> <span class=\"token class-name\">Ulid</span> <span class=\"token keyword\">value</span><span class=\"token punctuation\">)</span>\n\t<span class=\"token punctuation\">{</span>\n\t\tparameter<span class=\"token punctuation\">.</span>Value <span class=\"token operator\">=</span> <span class=\"token keyword\">value</span><span class=\"token punctuation\">.</span><span class=\"token function\">ToByteArray</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\t<span class=\"token punctuation\">}</span>\n\n\t<span class=\"token keyword\">public</span> <span class=\"token keyword\">override</span> <span class=\"token return-type class-name\">Ulid</span> <span class=\"token function\">Parse</span><span class=\"token punctuation\">(</span><span class=\"token class-name\"><span class=\"token keyword\">object</span></span> <span class=\"token keyword\">value</span><span class=\"token punctuation\">)</span>\n\t<span class=\"token punctuation\">{</span>\n\t\t<span class=\"token keyword\">return</span> Ulid<span class=\"token punctuation\">.</span><span class=\"token function\">New</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">(</span><span class=\"token keyword\">byte</span><span class=\"token punctuation\">[</span><span class=\"token punctuation\">]</span><span class=\"token punctuation\">)</span><span class=\"token keyword\">value</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\t<span class=\"token punctuation\">}</span>\n<span class=\"token punctuation\">}</span>\n\nDapper<span class=\"token punctuation\">.</span>SqlMapper<span class=\"token punctuation\">.</span><span class=\"token function\">AddTypeHandler</span><span class=\"token punctuation\">(</span><span class=\"token keyword\">new</span> <span class=\"token constructor-invocation class-name\">UlidTypeHandler</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span></code></pre><blockquote><h3 id=\"simplified-integration-with-companion-libraries-(v1.4.0%2B)\" tabindex=\"-1\">Simplified Integration with Companion Libraries (v1.4.0+)</h3><p>While writing custom type converters and handlers provides a clear view of the ORM plumbing, manual setup is no longer required for standard projects. With the release of <strong><a href=\"https://byteaether.github.io/2026/streamlining-net-data-layers-with-byteaetherulid-v140/\">ByteAether.Ulid v1.4.0</a></strong>, official companion packages are available on NuGet to handle registration and persistence strategies automatically:</p><ul><li><strong>Entity Framework Core:</strong> <a href=\"https://www.nuget.org/packages/ByteAether.Ulid.EntityFrameworkCore\" target=\"_blank\" rel=\"noopener noreferrer\"><code>ByteAether.Ulid.EntityFrameworkCore</code></a> provides global value converters and configuration extensions.</li><li><strong>LinqToDB:</strong> <a href=\"https://www.nuget.org/packages/ByteAether.Ulid.linq2db\" target=\"_blank\" rel=\"noopener noreferrer\"><code>ByteAether.Ulid.linq2db</code></a> registers global mappings directly on <code>DataOptions</code> (e.g., <code>.RegisterUlid(UlidStorageFormat.Binary)</code>).</li><li><strong>Dapper:</strong> <a href=\"https://www.nuget.org/packages/ByteAether.Ulid.Dapper\" target=\"_blank\" rel=\"noopener noreferrer\"><code>ByteAether.Ulid.Dapper</code></a> simplifies setup down to a single startup call (e.g., <code>DapperUlid.RegisterUlid()</code>).</li></ul><p>These integration packages also ensure consistent byte layout strategies (such as <code>SqlServerGuid</code> handling) across complex query types and sorting operations.</p></blockquote><h3 id=\"performance-metrics\" tabindex=\"-1\">Performance Metrics</h3><p>The ByteAether.Ulid library generates ULIDs efficiently with zero heap allocations, making it ideal for high-throughput scenarios like event sourcing or real-time analytics. Detailed benchmark results are available in its <a href=\"https://github.com/ByteAether/Ulid\" target=\"_blank\" rel=\"noopener noreferrer\">GitHub repository</a>.</p><h2 id=\"recommendations-and-conclusion\" tabindex=\"-1\">Recommendations and Conclusion</h2><h3 id=\"when-to-use-ulids\" tabindex=\"-1\">When to Use ULIDs</h3><p>ULIDs are particularly advantageous in:</p><ul><li><strong>Distributed Systems:</strong> Decentralized generation eliminates coordination overhead.</li><li><strong>Time-Series Workloads:</strong> Embedded timestamps simplify partitioning and range queries.</li><li><strong>Batch Processing:</strong> Monotonicity ensures data retains its logical insertion order.</li></ul><h3 id=\"why-byteaether.ulid%3F\" tabindex=\"-1\">Why ByteAether.Ulid?</h3><ul><li><strong>Speed:</strong> Generates ULIDs faster than other .NET libaries.</li><li><strong>Efficiency:</strong> Zero heap allocations reduce garbage collection pressure.</li><li><strong>Simplicity:</strong> Seamless integration with EF Core, Dapper, JSON serializers and more.</li></ul><h2 id=\"the-future-of-identifiers\" tabindex=\"-1\">The Future of Identifiers</h2><p>UUIDs and integer IDs remain viable for legacy use cases, but ULIDs address their core limitations: sortability, fragmentation, and scalability. For .NET developers, <strong><a href=\"https://github.com/ByteAether/Ulid\" target=\"_blank\" rel=\"noopener noreferrer\">ByteAether.Ulid</a></strong> offers a robust, future-proof solution that aligns application logic with database storage mechanics.</p><pre class=\"language-bash\"><code class=\"language-bash\"><span class=\"token comment\"># Install ByteAether.Ulid</span>\ndotnet <span class=\"token function\">add</span> package ByteAether.Ulid</code></pre><p>By adopting ULIDs, teams can achieve faster queries, reduced infrastructure costs, and architectures that scale effortlessly—proving that the right identifier can transform system design.</p>",
      "date_published": "2025-02-04T00:00:00Z"
    }
    ,
    {
      "id": "https://byteaether.github.io/2025/an-introduction-to-ulids-a-modern-identifier-for-software-systems/",
      "url": "https://byteaether.github.io/2025/an-introduction-to-ulids-a-modern-identifier-for-software-systems/",
      "title": "An Introduction to ULIDs: A Modern Identifier for Software Systems",
      "content_html": "<p>In the world of software development, identifiers are a cornerstone of data storage and retrieval. Whether you are designing a database, an API, or a distributed system, choosing the right identifier scheme is critical to ensuring performance, scalability, and reliability. Two widely used options are auto-incrementing integers and UUIDs (Universally Unique Identifiers). While each has its strengths, they also come with significant trade-offs. ULIDs (Universally Unique Lexicographically Sortable Identifiers) have emerged as a modern alternative that combines the best of both worlds. This article introduces ULIDs by first exploring the strengths and limitations of traditional identifier schemes and then delving into the advantages of ULIDs.</p><h2 id=\"the-strengths-and-limitations-of-auto-incrementing-integers\" tabindex=\"-1\">The Strengths and Limitations of Auto-Incrementing Integers</h2><p>Auto-incrementing integers have been a staple in relational databases for decades. They are simple to implement, easy to understand, and offer excellent performance in single-node systems. With each new row, the database generates a unique, sequential integer that serves as the primary key. This approach ensures:</p><ul><li><strong>Predictable Order:</strong> The sequential nature of integers makes data easy to read and maintain.</li><li><strong>Compact Storage:</strong> Integers require minimal storage space and are efficiently indexed.</li><li><strong>Fast Operations:</strong> Range queries and lookups based on integers are highly optimized.</li></ul><p>However, this simplicity comes at a cost when dealing with distributed systems or high-scale applications:</p><ul><li><strong>Centralized Coordination:</strong> Generating sequential integers requires a single point of coordination, which can become a bottleneck in distributed systems.</li><li><strong>Scalability Challenges:</strong> As the system grows, maintaining a central source of truth for generating identifiers becomes increasingly complex.</li><li><strong>Collision Risk:</strong> Without central coordination, ensuring uniqueness across nodes is impossible.</li></ul><h2 id=\"uuids%3A-strengths-and-weaknesses\" tabindex=\"-1\">UUIDs: Strengths and Weaknesses</h2><p>UUIDs were designed to address the distributed nature of modern systems. By using a combination of time, machine identifiers, and random numbers, UUIDs provide globally unique identifiers without requiring central coordination. Their advantages include:</p><ul><li><strong>Global Uniqueness:</strong> UUIDs ensure that identifiers generated on different machines or systems will never collide.</li><li><strong>Distributed Scalability:</strong> Nodes can independently generate identifiers, eliminating the need for centralized coordination.</li><li><strong>Standardization:</strong> UUIDs have a well-defined format and are supported across many programming languages and frameworks.</li></ul><p>However, the randomness inherent in UUIDs introduces its own set of challenges:</p><ul><li><strong>Index Fragmentation:</strong> UUIDs are effectively random, causing data to be scattered across database indexes. This leads to poor write performance and increased storage overhead.</li><li><strong>Human Unreadability:</strong> UUIDs are long and complex, making them difficult to read or debug.</li><li><strong>Inefficient Range Queries:</strong> UUIDs lack temporal ordering, complicating queries that rely on time-based filtering.</li></ul><h2 id=\"introducing-ulids%3A-the-best-of-both-worlds\" tabindex=\"-1\">Introducing ULIDs: The Best of Both Worlds</h2><p>ULIDs were designed to overcome the limitations of both auto-incrementing integers and UUIDs. By combining a 48-bit timestamp with an 80-bit random component, ULIDs achieve:</p><ul><li><strong>Global Uniqueness:</strong> Like UUIDs, ULIDs are unique across distributed systems.</li><li><strong>Temporal Ordering:</strong> The timestamp ensures that ULIDs are lexicographically sortable, preserving the chronological order of creation.</li><li><strong>Distributed Generation:</strong> ULIDs can be generated independently without requiring central coordination.</li><li><strong>Human Readability:</strong> ULIDs use <a href=\"https://www.crockford.com/base32.html\" target=\"_blank\" rel=\"noopener noreferrer\">Crockford's Base32 encoding</a>, which avoids ambiguous characters and makes the identifier more readable as a string.</li></ul><p>The official ULID specification, available on <a href=\"https://github.com/ulid/spec\" target=\"_blank\" rel=\"noopener noreferrer\">GitHub</a>, provides detailed guidelines for implementing ULIDs in various programming languages.</p><h2 id=\"the-role-of-crockford's-base32-encoding\" tabindex=\"-1\">The Role of Crockford's Base32 Encoding</h2><p>A key feature of ULIDs is their use of Crockford's Base32 encoding, which is designed to enhance human readability. This encoding scheme avoids visually similar characters, such as &quot;I&quot; and &quot;1&quot; or &quot;O&quot; and &quot;0,&quot; reducing the chance of errors when reading or transcribing ULIDs. Additionally, Base32-encoded ULIDs are case-insensitive, making them easier to handle in systems that do not preserve case.</p><p>By encoding ULIDs in Base32, developers can work with identifiers that are compact, consistent, and user-friendly, bridging the gap between machine efficiency and human usability.</p><h2 id=\"monotonicity%3A-preserving-order-in-high-throughput-systems\" tabindex=\"-1\">Monotonicity: Preserving Order in High-Throughput Systems</h2><p>ULIDs also address a unique challenge in high-throughput systems: ensuring that identifiers remain ordered even when multiple are generated within the same millisecond. The monotonicity feature of ULIDs guarantees that if multiple ULIDs share the same timestamp, they are incremented to maintain their lexicographical order. This ensures:</p><ul><li><strong>Consistency:</strong> Data remains ordered, even under high-concurrency scenarios.</li><li><strong>Query Efficiency:</strong> Time-based range queries continue to perform well, regardless of the volume of data generated.</li><li><strong>Reliable Sorting:</strong> Applications that rely on ordered identifiers, such as event streams or analytics platforms, benefit from monotonicity.</li></ul><h2 id=\"why-ulids-matter-for-modern-applications\" tabindex=\"-1\">Why ULIDs Matter for Modern Applications</h2><p>ULIDs are particularly well-suited for applications that require scalability, temporal ordering, and distributed generation. Use cases include:</p><ol><li><strong>Event Logging:</strong> Ensures efficient storage and retrieval of time-stamped log entries.</li><li><strong>Distributed Databases:</strong> Eliminates the need for central coordination while preserving order.</li><li><strong>Analytics and Reporting:</strong> Facilitates time-based filtering and aggregation of data.</li><li><strong>High-Throughput Systems:</strong> Maintains performance and order even under heavy write loads.</li></ol><h2 id=\"implementing-ulids-in-practice\" tabindex=\"-1\">Implementing ULIDs in Practice</h2><p>For developers looking to adopt ULIDs, a variety of libraries are available across programming languages. In particular, the <a href=\"https://github.com/ByteAether/Ulid\" target=\"_blank\" rel=\"noopener noreferrer\">ByteAether's Ulid GitHub repository</a> offers a high-performance C# implementation.</p><p>By following the official specification and leveraging well-maintained libraries, developers can integrate ULIDs into their systems with minimal effort and maximum reliability.</p><h2 id=\"conclusion\" tabindex=\"-1\">Conclusion</h2><p>ULIDs offer a modern, versatile solution to the challenges of identifier generation in software systems. By combining the benefits of auto-incrementing integers and UUIDs while avoiding their pitfalls, ULIDs provide a unique identifier scheme that is globally unique, lexicographically sortable, and human-readable. With features like Crockford's Base32 encoding and monotonicity, ULIDs are tailored for the demands of modern applications, from distributed systems to time-based analytics.</p><p>Whether you are building a new system or optimizing an existing one, ULIDs represent a compelling option for identifier management. With their growing ecosystem of libraries and strong community support, they are poised to become a standard choice for developers seeking scalability, performance, and simplicity.</p><hr/><p>Start integrating ULIDs into your .NET applications today with <a href=\"https://github.com/ByteAether/Ulid\" target=\"_blank\" rel=\"noopener noreferrer\">ByteAether.Ulid</a>. Let us know your thoughts and feedback—we’re excited to see what you build!</p>",
      "date_published": "2025-01-28T00:00:00Z"
    }
    ,
    {
      "id": "https://byteaether.github.io/2025/announcing-byteaetherulid-v100-a-high-performance-net-ulid-library-for-modern-applications/",
      "url": "https://byteaether.github.io/2025/announcing-byteaetherulid-v100-a-high-performance-net-ulid-library-for-modern-applications/",
      "title": "Announcing ByteAether.Ulid v1.0.0: A High-Performance .NET ULID Library for Modern Applications",
      "content_html": "<p>Frustrated with GUIDs that aren’t sortable or human-readable? Meet ByteAether.Ulid - a ULID library designed to simplify your .NET development.</p><p>We’re excited to introduce <a href=\"https://github.com/ByteAether/Ulid\" target=\"_blank\" rel=\"noopener noreferrer\">ByteAether.Ulid v1.0.0</a>, a high-performance .NET library for generating and working with <a href=\"https://github.com/ulid/spec\" target=\"_blank\" rel=\"noopener noreferrer\">ULIDs (Universally Unique Lexicographically Sortable Identifiers)</a>.</p><p>For developers managing distributed systems or working with time-sensitive data, ULIDs offer the perfect blend of uniqueness, readability, and sortability. ByteAether.Ulid takes it further by delivering exceptional performance, seamless integration with modern .NET projects, and comprehensive reliability.</p><h2 id=\"why-ulids%2C-and-why-byteaether.ulid%3F\" tabindex=\"-1\">Why ULIDs, and Why ByteAether.Ulid?</h2><p>ULIDs provide unique identifiers that are:</p><ul><li><strong>Globally Unique:</strong> No risk of collisions, even in distributed systems.</li><li><strong>Sortable:</strong> Lexicographically ordered, making them ideal for time-based sorting.</li><li><strong>Human-Readable:</strong> Unlike GUIDs, ULIDs use a base32 encoding that’s easy to interpret.</li></ul><p>ByteAether.Ulid goes beyond standard implementations:</p><table><thead><tr><th>Feature</th><th>GUIDs</th><th>Integer IDs</th><th>ULIDs</th><th>ByteAether.Ulid Enhancements</th></tr></thead><tbody><tr><td>Global Uniqueness</td><td>✅ Yes</td><td>❌ No</td><td>✅ Yes</td><td>✅ Yes</td></tr><tr><td>Time-Based Sortability</td><td>❌ No</td><td>✅ Yes</td><td>✅ Yes</td><td>✅ Yes</td></tr><tr><td>Human-Readable Format</td><td>❌ No</td><td>✅ Yes</td><td>✅ Yes</td><td>✅ Yes</td></tr><tr><td>Overflow Handling</td><td>❌ N/A</td><td>❌ N/A</td><td>❌ Sometimes</td><td>✅ Graceful &amp; Reliable</td></tr></tbody></table><p>ByteAether.Ulid fully adheres to the <a href=\"https://github.com/ulid/spec\" target=\"_blank\" rel=\"noopener noreferrer\">ULID specification</a> while addressing common pitfalls, such as random-part overflow, ensuring a truly error-free experience.</p><h2 id=\"key-features-of-byteaether.ulid\" tabindex=\"-1\">Key Features of ByteAether.Ulid</h2><h3 id=\"1.-error-free-ulid-generation\" tabindex=\"-1\">1. Error-Free ULID Generation</h3><p>Handles edge cases gracefully, like random-part overflow, by incrementing timestamps to maintain monotonicity without errors.</p><h3 id=\"2.-high-performance-design\" tabindex=\"-1\">2. High-Performance Design</h3><p>ByteAether.Ulid is optimized for speed and low memory usage, outperforming other libraries like NetUlid, NUlid, and Cysharp’s Ulid.</p><h3 id=\"3.-seamless-.net-integration\" tabindex=\"-1\">3. Seamless .NET Integration</h3><ul><li>Supports .NET versions from .NET Standard 2.0 to .NET 9.0.</li><li>Native support for ASP.NET Core route parameters.</li><li>Fully compatible with <code>System.Text.Json</code> serialization and deserialization.</li></ul><h3 id=\"4.-versatile-conversion-methods\" tabindex=\"-1\">4. Versatile Conversion Methods</h3><p>Effortlessly convert ULIDs to and from:</p><ul><li>Byte arrays</li><li>GUIDs</li><li>Strings</li></ul><h2 id=\"getting-started\" tabindex=\"-1\">Getting Started</h2><h3 id=\"installation\" tabindex=\"-1\">Installation</h3><p>Getting started is as simple as installing the NuGet package:</p><p><code>dotnet add package ByteAether.Ulid</code></p><p>Run <code>dotnet list package</code> to verify the installation. Visit the <a href=\"https://www.nuget.org/packages/ByteAether.Ulid/\" target=\"_blank\" rel=\"noopener noreferrer\">NuGet page</a> for more details.</p><h3 id=\"usage\" tabindex=\"-1\">Usage</h3><p>Here’s how simple it is to generate and use ULIDs with ByteAether.Ulid:</p><pre class=\"language-csharp\"><code class=\"language-csharp\"><span class=\"token keyword\">using</span> <span class=\"token namespace\">System</span><span class=\"token punctuation\">;</span>\n\n<span class=\"token keyword\">class</span> <span class=\"token class-name\">Program</span>\n<span class=\"token punctuation\">{</span>\n\t<span class=\"token keyword\">static</span> <span class=\"token return-type class-name\"><span class=\"token keyword\">void</span></span> <span class=\"token function\">Main</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span>\n\t<span class=\"token punctuation\">{</span>\n\t\t<span class=\"token comment\">// Create a new ULID</span>\n\t\t<span class=\"token class-name\"><span class=\"token keyword\">var</span></span> ulid <span class=\"token operator\">=</span> Ulid<span class=\"token punctuation\">.</span><span class=\"token function\">New</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\n\t\t<span class=\"token comment\">// Convert to string and back</span>\n\t\t<span class=\"token class-name\"><span class=\"token keyword\">string</span></span> ulidString <span class=\"token operator\">=</span> ulid<span class=\"token punctuation\">.</span><span class=\"token function\">ToString</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\t\t<span class=\"token class-name\"><span class=\"token keyword\">var</span></span> ulidFromString <span class=\"token operator\">=</span> Ulid<span class=\"token punctuation\">.</span><span class=\"token function\">Parse</span><span class=\"token punctuation\">(</span>ulidString<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\n\t\tConsole<span class=\"token punctuation\">.</span><span class=\"token function\">WriteLine</span><span class=\"token punctuation\">(</span><span class=\"token interpolation-string\"><span class=\"token string\">$\"Generated ULID: </span><span class=\"token interpolation\"><span class=\"token punctuation\">{</span><span class=\"token expression language-csharp\">ulid</span><span class=\"token punctuation\">}</span></span><span class=\"token string\">\"</span></span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\t<span class=\"token punctuation\">}</span>\n<span class=\"token punctuation\">}</span></code></pre><p>For detailed usage instructions and a complete API reference, check out the <a href=\"https://github.com/ByteAether/Ulid\" target=\"_blank\" rel=\"noopener noreferrer\">GitHub repository</a>.</p><h2 id=\"performance-highlights\" tabindex=\"-1\">Performance Highlights</h2><p>ByteAether.Ulid outpaces competitors while maintaining strict adherence to the ULID specification.</p><table><thead><tr><th>Library</th><th>Generation Speed</th><th>Compliance</th><th>Error Handling</th></tr></thead><tbody><tr><td>ByteAether.Ulid</td><td>✅ Fastest</td><td>✅ Full</td><td>✅ Graceful</td></tr><tr><td>NetUlid</td><td>⚠️ Slower</td><td>✅ Full</td><td>⚠️ Limited</td></tr><tr><td>NUlid</td><td>⚠️ Slower</td><td>✅ Full</td><td>⚠️ Limited</td></tr><tr><td>Cysharp.Ulid</td><td>⚠️ Fast, Non-secure, Non-monotonic</td><td>❌ Incomplete</td><td>⚠️ Limited</td></tr></tbody></table><p>For a full breakdown of benchmarks and testing methodology, visit our <a href=\"https://github.com/ByteAether/Ulid\" target=\"_blank\" rel=\"noopener noreferrer\">GitHub repository</a>.</p><h2 id=\"open-source-and-contributions\" tabindex=\"-1\">Open Source and Contributions</h2><p>ByteAether.Ulid is fully open-source and licensed under the MIT License. Contributions are welcome! Whether you want to report bugs, suggest features, or submit a pull request, we’d love your input.</p><p><a href=\"https://github.com/ByteAether/Ulid/issues\" target=\"_blank\" rel=\"noopener noreferrer\">👉 Submit an Issue</a><br/><a href=\"https://github.com/ByteAether/Ulid/pulls\" target=\"_blank\" rel=\"noopener noreferrer\">👉 Contribute Code</a></p><h2 id=\"what%E2%80%99s-next%3F\" tabindex=\"-1\">What’s Next?</h2><p>Stay tuned for deep dives into:</p><ul><li>Why ULIDs are a game-changer for modern databases.</li><li>Optimizing performance in distributed systems with ULIDs.</li><li>Real-world use cases for ULIDs in .NET applications.</li></ul><p>Start simplifying your .NET projects today. Install <a href=\"https://github.com/ByteAether/Ulid\" target=\"_blank\" rel=\"noopener noreferrer\">ByteAether.Ulid</a> and experience the difference!</p>",
      "date_published": "2025-01-21T00:00:00Z"
    }
    
  ]
}