<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: GigAHerZ</title>
    <description>The latest articles on DEV Community by GigAHerZ (@gigaherz).</description>
    <link>https://dev.to/gigaherz</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F2786169%2F3ac5be30-b6f3-4018-9747-5cb23c3f497a.jpg</url>
      <title>DEV Community: GigAHerZ</title>
      <link>https://dev.to/gigaherz</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/gigaherz"/>
    <language>en</language>
    <item>
      <title>Why I Over-Engineered a ULID Library for .NET</title>
      <dc:creator>GigAHerZ</dc:creator>
      <pubDate>Tue, 07 Apr 2026 13:30:00 +0000</pubDate>
      <link>https://dev.to/gigaherz/why-i-over-engineered-a-ulid-library-for-net-38gh</link>
      <guid>https://dev.to/gigaherz/why-i-over-engineered-a-ulid-library-for-net-38gh</guid>
      <description>&lt;p&gt;Unique identifiers are often treated as an afterthought, but for senior engineers and architects, they represent a critical choice in system design. "Good enough" identifiers are a ticking time bomb of index fragmentation and degraded performance.&lt;/p&gt;

&lt;h1&gt;
  
  
  Why ULID over UUIDv7?
&lt;/h1&gt;

&lt;p&gt;While UUIDv7 is a step forward, its specification makes monotonicity optional. During high-concurrency bursts, many generators sacrifice order to stay fast. &lt;a href="https://github.com/ByteAether/Ulid" rel="noopener noreferrer"&gt;ByteAether.Ulid&lt;/a&gt; refuses this compromise. By mandating strict lexicographical sortability, it ensures your database indexes remain optimized and sequential.&lt;/p&gt;

&lt;h1&gt;
  
  
  Solving the Overflow Problem
&lt;/h1&gt;

&lt;p&gt;A common failure point in ULID implementations is the 80-bit random component overflow. High-volume systems can easily exhaust the random space within a single millisecond, causing standard libraries to &lt;a href="https://github.com/ulid/spec?tab=readme-ov-file#monotonicity" rel="noopener noreferrer"&gt;throw an &lt;code&gt;OverflowException&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;ByteAether.Ulid&lt;/strong&gt; handles this by automatically incrementing the timestamp component. This ensures unique, sorted IDs continue to generate even under extreme load, providing a level of resilience that standard implementations lack.&lt;/p&gt;

&lt;h1&gt;
  
  
  Performance via CAS Strategy
&lt;/h1&gt;

&lt;p&gt;In high-throughput environments, traditional locking mechanisms introduce significant overhead and latency. ByteAether.Ulid utilizes a &lt;strong&gt;lock-free compare-and-exchange (CAS)&lt;/strong&gt; strategy to manage state.&lt;/p&gt;

&lt;p&gt;By avoiding heavy OS-level locks and using hardware-level atomics to achieve the same synchronization, we eliminate the latency typical of normal locking mechanisms. This allows threads to resolve state changes with minimal friction, maximizing CPU efficiency while maintaining strict thread safety.&lt;/p&gt;

&lt;h1&gt;
  
  
  Strategic Features
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Anti-Enumeration:&lt;/strong&gt; Configurable random increments prevent attackers from &lt;a href="https://github.com/ulid/spec/issues/105" rel="noopener noreferrer"&gt;guessing sequential IDs&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Temporal Queries:&lt;/strong&gt; Use &lt;code&gt;Ulid.MinAt()&lt;/code&gt; and &lt;code&gt;Ulid.MaxAt()&lt;/code&gt; to perform range queries on your primary key without needing a &lt;code&gt;CreatedAt&lt;/code&gt; column.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Zero Allocations:&lt;/strong&gt; Optimized for standard generation paths to reduce GC pressure.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I have documented the full engineering rationale and performance benchmarks in &lt;a href="https://byteaether.github.io/2026/the-architecture-of-obsession-why-byteaetherulid-is-the-last-identifier-library-you-will-ever-need/" rel="noopener noreferrer"&gt;a detailed post on my blog&lt;/a&gt;. If you are interested in the nuances of distributed system design, I invite you to read the full version.&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>csharp</category>
      <category>webdev</category>
      <category>architecture</category>
    </item>
    <item>
      <title>Moving Beyond O(N^2 log N) for Weighted Random Sorting</title>
      <dc:creator>GigAHerZ</dc:creator>
      <pubDate>Wed, 07 Jan 2026 14:30:00 +0000</pubDate>
      <link>https://dev.to/gigaherz/moving-beyond-on2-log-n-for-weighted-random-sorting-2hc9</link>
      <guid>https://dev.to/gigaherz/moving-beyond-on2-log-n-for-weighted-random-sorting-2hc9</guid>
      <description>&lt;p&gt;In high-volume traffic routing, we often need a "fail-over" list: a fully sorted list of destinations where the first item is the most likely candidate, but subsequent items are available if the primary fails.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Starvation Problem
&lt;/h3&gt;

&lt;p&gt;Sorting purely by success rate is a mistake. It directs 100% of traffic to the top service, leaving others with no traffic to prove they have recovered from a previous failure. We need weighted randomization to ensure every service gets "exploration" traffic.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Better Way: Efraimidis-Spirakis
&lt;/h3&gt;

&lt;p&gt;Instead of an iterative loop that picks and removes items, we can use a specialized form of Reservoir Sampling. The Efraimidis-Spirakis paper demonstrates that we can generate a weighted permutation in a single pass.&lt;/p&gt;

&lt;p&gt;The logic is simple:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Generate a random double &lt;code&gt;u&lt;/code&gt; between 0 and 1.&lt;/li&gt;
&lt;li&gt;Calculate a sort key: &lt;code&gt;u^(1/weight)&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Sort the list by this key in descending order.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Scalability and Streams
&lt;/h3&gt;

&lt;p&gt;This approach reduces complexity to O(N log N) and works perfectly for data streams. You can assign a sort key to an item the moment it arrives, making it ideal for high-throughput systems where performance is critical.&lt;/p&gt;

&lt;p&gt;I've written a more detailed post on my personal blog that compares C# implementations and dives deeper into the math.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Check out the full post on my blog: &lt;a href="https://byteaether.github.io/2026/the-weight-of-decisions-solving-weighted-random-sorting-at-scale/" rel="noopener noreferrer"&gt;https://byteaether.github.io/2026/the-weight-of-decisions-solving-weighted-random-sorting-at-scale/&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>programming</category>
      <category>algorithms</category>
      <category>performance</category>
      <category>dotnet</category>
    </item>
    <item>
      <title>ULID 1.3.2 is here: .NET 10 support, C# 14 field keyword for zero-overhead validation, and intelligent overflow prevention. Essential reading for .NET architects &amp; senior engineers. #dotnet #csharp #ulid #performance</title>
      <dc:creator>GigAHerZ</dc:creator>
      <pubDate>Thu, 18 Dec 2025 15:14:45 +0000</pubDate>
      <link>https://dev.to/gigaherz/ulid-132-is-here-net-10-support-c-14-field-keyword-for-zero-overhead-validation-and-1ip3</link>
      <guid>https://dev.to/gigaherz/ulid-132-is-here-net-10-support-c-14-field-keyword-for-zero-overhead-validation-and-1ip3</guid>
      <description>&lt;div class="ltag__link"&gt;
  &lt;a href="/gigaherz" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__pic"&gt;
      &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F2786169%2F3ac5be30-b6f3-4018-9747-5cb23c3f497a.jpg" alt="gigaherz"&gt;
    &lt;/div&gt;
  &lt;/a&gt;
  &lt;a href="https://dev.to/gigaherz/byteaetherulid-132-net-10-support-c-14s-field-and-engineering-zero-overhead-ids-3pn7" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__content"&gt;
      &lt;h2&gt;ByteAether.Ulid 1.3.2: .NET 10 Support, C# 14's Field, and Engineering Zero-Overhead IDs&lt;/h2&gt;
      &lt;h3&gt;GigAHerZ ・ Nov 14&lt;/h3&gt;
      &lt;div class="ltag__link__taglist"&gt;
        &lt;span class="ltag__link__tag"&gt;#csharp&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#dotnet&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#webdev&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#news&lt;/span&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
&lt;/div&gt;


</description>
      <category>csharp</category>
      <category>dotnet</category>
      <category>webdev</category>
      <category>news</category>
    </item>
    <item>
      <title>Finalizing the Enterprise DAL series! We implement **Automated User Auditing** (`CreatedByUserId`/`ModifiedByUserId`) and review the full architecture that automated Soft-Delete, Multi-Tenancy, and RLS. C# &amp; Linq2Db. #dotnet #csharp #architecture #sql</title>
      <dc:creator>GigAHerZ</dc:creator>
      <pubDate>Thu, 04 Dec 2025 22:05:25 +0000</pubDate>
      <link>https://dev.to/gigaherz/finalizing-the-enterprise-dal-series-we-implement-automated-user-auditing-55</link>
      <guid>https://dev.to/gigaherz/finalizing-the-enterprise-dal-series-we-implement-automated-user-auditing-55</guid>
      <description>&lt;p&gt;

&lt;/p&gt;
&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/gigaherz/enterprise-dal-final-automated-user-auditing-and-architectural-retrospective-3jpc" class="crayons-story__hidden-navigation-link"&gt;Enterprise DAL Final: Automated User Auditing and Architectural Retrospective&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;

          &lt;a href="/gigaherz" class="crayons-avatar  crayons-avatar--l  "&gt;
            &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F2786169%2F3ac5be30-b6f3-4018-9747-5cb23c3f497a.jpg" alt="gigaherz profile" class="crayons-avatar__image"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/gigaherz" class="crayons-story__secondary fw-medium m:hidden"&gt;
              GigAHerZ
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                GigAHerZ
                
              
              &lt;div id="story-author-preview-content-3078804" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/gigaherz" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&gt;
                        &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F2786169%2F3ac5be30-b6f3-4018-9747-5cb23c3f497a.jpg" class="crayons-avatar__image" alt=""&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;GigAHerZ&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.to/gigaherz/enterprise-dal-final-automated-user-auditing-and-architectural-retrospective-3jpc" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Dec 2 '25&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/gigaherz/enterprise-dal-final-automated-user-auditing-and-architectural-retrospective-3jpc" id="article-link-3078804"&gt;
          Enterprise DAL Final: Automated User Auditing and Architectural Retrospective
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/database"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;database&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/architecture"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;architecture&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/automation"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;automation&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/csharp"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;csharp&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
          &lt;a href="https://dev.to/gigaherz/enterprise-dal-final-automated-user-auditing-and-architectural-retrospective-3jpc" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left"&gt;
            &lt;div class="multiple_reactions_aggregate"&gt;
              &lt;span class="multiple_reactions_icons_container"&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/fire-f60e7a582391810302117f987b22a8ef04a2fe0df7e3258a5f49332df1cec71e.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/exploding-head-daceb38d627e6ae9b730f36a1e390fca556a4289d5a41abb2c35068ad3e2c4b5.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/sparkle-heart-5f9bee3767e18deb1bb725290cb151c25234768a0e9a2bd39370c382d02920cf.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
              &lt;/span&gt;
              &lt;span class="aggregate_reactions_counter"&gt;3&lt;span class="hidden s:inline"&gt; reactions&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/a&gt;
            &lt;a href="https://dev.to/gigaherz/enterprise-dal-final-automated-user-auditing-and-architectural-retrospective-3jpc#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              Comments


              &lt;span class="hidden s:inline"&gt;Add Comment&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            2 min read
          &lt;/small&gt;
            
              &lt;span class="bm-initial"&gt;
                

              &lt;/span&gt;
              &lt;span class="bm-success"&gt;
                

              &lt;/span&gt;
            
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;




</description>
      <category>database</category>
      <category>architecture</category>
      <category>automation</category>
      <category>csharp</category>
    </item>
    <item>
      <title>Enterprise DAL Final: Automated User Auditing and Architectural Retrospective</title>
      <dc:creator>GigAHerZ</dc:creator>
      <pubDate>Tue, 02 Dec 2025 13:00:00 +0000</pubDate>
      <link>https://dev.to/gigaherz/enterprise-dal-final-automated-user-auditing-and-architectural-retrospective-3jpc</link>
      <guid>https://dev.to/gigaherz/enterprise-dal-final-automated-user-auditing-and-architectural-retrospective-3jpc</guid>
      <description>&lt;p&gt;This post marks the end of our &lt;a href="https://byteaether.github.io/series/enterprise-dal/" rel="noopener noreferrer"&gt;series&lt;/a&gt; on constructing an enterprise-grade Data Access Layer (DAL) in C# with Linq2Db. Our goal was to create a DAL that is secure and resilient by automatically handling crucial cross-cutting concerns.&lt;/p&gt;

&lt;h2&gt;
  
  
  Implementing Automated User Auditing
&lt;/h2&gt;

&lt;p&gt;To achieve compliance and enhance debugging, we implement &lt;strong&gt;Automated User Auditing&lt;/strong&gt;. This feature automatically populates &lt;code&gt;CreatedByUserId&lt;/code&gt; and &lt;code&gt;ModifiedByUserId&lt;/code&gt; fields with the current user's unique identifier (&lt;code&gt;Ulid&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;The implementation is integrated into our existing interface-driven architecture:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Contracts:&lt;/strong&gt; New interfaces (&lt;code&gt;IUserCreatable&lt;/code&gt;, &lt;code&gt;IUserModifiable&lt;/code&gt;) define the required properties.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Automation:&lt;/strong&gt; Our custom scaffolding ensures that any entity table with &lt;code&gt;created_by_user_id&lt;/code&gt;/&lt;code&gt;modified_by_user_id&lt;/code&gt; columns automatically implements these interfaces.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Injection Logic:&lt;/strong&gt; We update our &lt;code&gt;CrudExtensions&lt;/code&gt; to inspect the entity on create/modify operations. If the entity implements the auditing interface, the current &lt;code&gt;UserId&lt;/code&gt; from the request-scoped context is injected before the database operation executes.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Crucial detail for Architects:&lt;/strong&gt; We detail a necessary, advanced technique using &lt;code&gt;(LinqToDB.Internal.Linq).Internals.GetDataContext(source)&lt;/code&gt; to correctly inject the &lt;code&gt;ModifiedByUserId&lt;/code&gt; user ID during &lt;strong&gt;fluent batch UPDATE&lt;/strong&gt; operations (&lt;code&gt;IUpdatable&amp;lt;T&amp;gt;&lt;/code&gt;). This ensures auditing integrity across all modification types.&lt;/p&gt;

&lt;h2&gt;
  
  
  Verdict: 100% Goal Achievement
&lt;/h2&gt;

&lt;p&gt;The final architecture successfully automated every requirement initially set for a perfect enterprise DAL:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Ubiquitous Filtering:&lt;/strong&gt; Soft-Delete, Multi-Tenancy, and Row-Level Security are enforced by a highly composable global query filter system, transparently generating the correct SQL joins and &lt;code&gt;WHERE&lt;/code&gt; clauses for &lt;em&gt;all&lt;/em&gt; read operations.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Projected Security:&lt;/strong&gt; The use of Linq2Db’s projection capabilities allows for complex security and tenancy rules to be resolved contextually from related entities, a key differentiator.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Auditing:&lt;/strong&gt; Timestamp and User auditing are handled automatically on creation and modification.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This robust DAL abstracts away security and compliance boilerplate, letting developers focus purely on business logic.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;For the full implementation details, including source code and an in-depth explanation of the global query filter system,&lt;/strong&gt; &lt;a href="https://byteaether.github.io/2025/building-an-enterprise-data-access-layer-automated-user-auditing-and-series-wrap-up/" rel="noopener noreferrer"&gt;read the complete article on my blog&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>database</category>
      <category>architecture</category>
      <category>automation</category>
      <category>csharp</category>
    </item>
    <item>
      <title>Stop writing RLS filters. Build a composable DAL that enforces Row-Level Security automatically. Fail-closed, projected permissions that compose perfectly with Soft-Deletes &amp; Multi-Tenancy. Essential reading for architects. See the generated SQL!</title>
      <dc:creator>GigAHerZ</dc:creator>
      <pubDate>Mon, 01 Dec 2025 13:53:20 +0000</pubDate>
      <link>https://dev.to/gigaherz/stop-writing-rls-filters-build-a-composable-dal-that-enforces-row-level-security-automatically-49m8</link>
      <guid>https://dev.to/gigaherz/stop-writing-rls-filters-build-a-composable-dal-that-enforces-row-level-security-automatically-49m8</guid>
      <description>&lt;div class="ltag__link"&gt;
  &lt;a href="/gigaherz" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__pic"&gt;
      &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F2786169%2F3ac5be30-b6f3-4018-9747-5cb23c3f497a.jpg" alt="gigaherz"&gt;
    &lt;/div&gt;
  &lt;/a&gt;
  &lt;a href="https://dev.to/gigaherz/building-composable-rls-enterprise-data-security-on-autopilot-abj" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__content"&gt;
      &lt;h2&gt;Building Composable RLS: Enterprise Data Security on Autopilot&lt;/h2&gt;
      &lt;h3&gt;GigAHerZ ・ Nov 25&lt;/h3&gt;
      &lt;div class="ltag__link__taglist"&gt;
        &lt;span class="ltag__link__tag"&gt;#dotnet&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#architecture&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#csharp&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#security&lt;/span&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
&lt;/div&gt;


</description>
      <category>dotnet</category>
      <category>architecture</category>
      <category>csharp</category>
      <category>security</category>
    </item>
    <item>
      <title>Building Composable RLS: Enterprise Data Security on Autopilot</title>
      <dc:creator>GigAHerZ</dc:creator>
      <pubDate>Tue, 25 Nov 2025 14:00:00 +0000</pubDate>
      <link>https://dev.to/gigaherz/building-composable-rls-enterprise-data-security-on-autopilot-abj</link>
      <guid>https://dev.to/gigaherz/building-composable-rls-enterprise-data-security-on-autopilot-abj</guid>
      <description>&lt;p&gt;Enterprise applications require rigorous data integrity and security, but manually implementing these cross-cutting concerns is error-prone. This post, the (almost) final in &lt;a href="https://byteaether.github.io/series/enterprise-dal/" rel="noopener noreferrer"&gt;our series on building an automated Data Access Layer (DAL)&lt;/a&gt;, focuses on implementing the most challenging requirement: &lt;strong&gt;Row-Level Security (RLS)&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;We build upon our existing system of composable global query filters, extending it to enforce entity-based RLS automatically. This means a developer writes a simple query, and the DAL guarantees that only records accessible to the current user are returned, even for complex joins.&lt;/p&gt;

&lt;h2&gt;
  
  
  The RLS Contract: &lt;code&gt;IProtected&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;To enable this, we update our &lt;code&gt;IDbCtx&lt;/code&gt; to carry the authenticated user's &lt;code&gt;Ulid? UserId&lt;/code&gt;. A key security choice is to adopt a &lt;strong&gt;"fail-closed"&lt;/strong&gt; stance: if &lt;code&gt;UserId&lt;/code&gt; is null, the query will be filtered to return no rows.&lt;/p&gt;

&lt;p&gt;We define the security behavior with the &lt;code&gt;IProtected&lt;/code&gt; interface.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;EntityFilter&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IProtected&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="k"&gt;nameof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Filter&lt;/span&gt;&lt;span class="p"&gt;))]&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;IProtected&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// The entity must expose the ID used for the permission check.&lt;/span&gt;
    &lt;span class="n"&gt;Ulid&lt;/span&gt; &lt;span class="nf"&gt;GetPermissionObjectId&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="c1"&gt;// Filter method dynamically applies an INNER JOIN to permissions table.&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The filter method attached via &lt;code&gt;[EntityFilter]&lt;/code&gt; performs two critical steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Unauthenticated Check:&lt;/strong&gt; If no &lt;code&gt;UserId&lt;/code&gt; is set in the context, it returns &lt;code&gt;q.Where(_ =&amp;gt; false)&lt;/code&gt;. Access denied.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Permission Join:&lt;/strong&gt; If a user is present, it executes an &lt;code&gt;InnerJoin&lt;/code&gt; between the entity query and the &lt;code&gt;IPermissionEntity&lt;/code&gt; table. This join is conditional on &lt;code&gt;perm.ObjectId&lt;/code&gt; matching the entity's object ID, and &lt;code&gt;perm.SubjectId&lt;/code&gt; matching the current user's ID.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Projected Permissions for Scalability
&lt;/h2&gt;

&lt;p&gt;What if permissions are hierarchical? For instance, a user is granted access to a &lt;code&gt;Post&lt;/code&gt;, but needs to query its child &lt;code&gt;Comment&lt;/code&gt;s. Our architecture handles this by allowing the &lt;code&gt;Comment&lt;/code&gt; entity to "project" its security check to its parent's ID.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;partial&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Comment&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;IProtected&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Tells the RLS filter to use the Post ID for the security check.&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;ExpressionMethod&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;nameof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;GetPermissionObjectIdExpression&lt;/span&gt;&lt;span class="p"&gt;))]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;Ulid&lt;/span&gt; &lt;span class="nf"&gt;GetPermissionObjectId&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="n"&gt;Expression&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Func&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Comment&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Ulid&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;GetPermissionObjectIdExpression&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The DAL automatically converts this method call into an expression tree that joins the &lt;code&gt;Comment&lt;/code&gt; table to the &lt;code&gt;Post&lt;/code&gt; table, then joins the &lt;code&gt;Post&lt;/code&gt; to the &lt;code&gt;Permission&lt;/code&gt; table. All the complexity of multi-table security is handled by the DAL framework, not the application code.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Result: Guaranteed Compliance
&lt;/h2&gt;

&lt;p&gt;When a developer executes a simple &lt;code&gt;ModifyAsync()&lt;/code&gt; on a protected, tenanted, and soft-deletable &lt;code&gt;Comment&lt;/code&gt; entity, the DAL composes &lt;em&gt;all&lt;/em&gt; four concerns into a single, efficient, and &lt;strong&gt;guaranteed-correct&lt;/strong&gt; SQL statement. This eliminates common security and data integrity vulnerabilities from the application layer.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;For the full source code, the detailed filter logic, and the final generated SQL, check out &lt;a href="https://byteaether.github.io/2025/building-an-enterprise-data-access-layer-composable-row-level-security/" rel="noopener noreferrer"&gt;the complete article on our blog&lt;/a&gt;.&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>architecture</category>
      <category>csharp</category>
      <category>security</category>
    </item>
    <item>
      <title>Architects: Stop relying on business logic for multi-tenancy. We show how to enforce non-bypassable data isolation at the DAL using C#, Linq2Db, and expression trees. Essential security for enterprise apps!

#multitenancy #csharp #linq2db</title>
      <dc:creator>GigAHerZ</dc:creator>
      <pubDate>Mon, 24 Nov 2025 12:42:27 +0000</pubDate>
      <link>https://dev.to/gigaherz/architects-stop-relying-on-business-logic-for-multi-tenancy-we-show-how-to-enforce-non-bypassable-1f3f</link>
      <guid>https://dev.to/gigaherz/architects-stop-relying-on-business-logic-for-multi-tenancy-we-show-how-to-enforce-non-bypassable-1f3f</guid>
      <description>&lt;div class="ltag__link"&gt;
  &lt;a href="/gigaherz" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__pic"&gt;
      &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F2786169%2F3ac5be30-b6f3-4018-9747-5cb23c3f497a.jpg" alt="gigaherz"&gt;
    &lt;/div&gt;
  &lt;/a&gt;
  &lt;a href="https://dev.to/gigaherz/building-a-secure-dal-composable-multi-tenancy-filtering-with-c-and-linq2db-19lo" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__content"&gt;
      &lt;h2&gt;Building a Secure DAL: Composable Multi-Tenancy Filtering with C# and Linq2Db&lt;/h2&gt;
      &lt;h3&gt;GigAHerZ ・ Nov 13&lt;/h3&gt;
      &lt;div class="ltag__link__taglist"&gt;
        &lt;span class="ltag__link__tag"&gt;#csharp&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#dotnet&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#security&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#architecture&lt;/span&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
&lt;/div&gt;


</description>
      <category>csharp</category>
      <category>dotnet</category>
      <category>security</category>
      <category>architecture</category>
    </item>
    <item>
      <title>ByteAether.Ulid 1.3.2: .NET 10 Support, C# 14's Field, and Engineering Zero-Overhead IDs</title>
      <dc:creator>GigAHerZ</dc:creator>
      <pubDate>Fri, 14 Nov 2025 13:00:00 +0000</pubDate>
      <link>https://dev.to/gigaherz/byteaetherulid-132-net-10-support-c-14s-field-and-engineering-zero-overhead-ids-3pn7</link>
      <guid>https://dev.to/gigaherz/byteaetherulid-132-net-10-support-c-14s-field-and-engineering-zero-overhead-ids-3pn7</guid>
      <description>&lt;p&gt;We are thrilled to announce the release of &lt;strong&gt;&lt;a href="https://github.com/ByteAether/Ulid" rel="noopener noreferrer"&gt;ByteAether.Ulid&lt;/a&gt; version 1.3.2&lt;/strong&gt;, now available on &lt;a href="https://www.nuget.org/packages/ByteAether.Ulid/" rel="noopener noreferrer"&gt;NuGet&lt;/a&gt;. This new version introduces full support for &lt;strong&gt;.NET 10&lt;/strong&gt; and finalizes our aggressive adoption of modern C# features to deliver the fastest, most secure, and most spec-compliant ULID implementation in .NET.&lt;/p&gt;

&lt;p&gt;Our library is engineered for senior developers and architects focused on high-throughput services. We tackle critical identifier generation problems, including robust overflow handling and secure monotonic increments.&lt;/p&gt;

&lt;h2&gt;
  
  
  Dedicated .NET 10 Binaries
&lt;/h2&gt;

&lt;p&gt;The primary feature of 1.3.2 is the inclusion of build artifacts specifically compiled against the &lt;strong&gt;.NET 10 SDK&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Our &lt;strong&gt;multi-targeting strategy&lt;/strong&gt; is fundamental to our performance guarantee. We don't ship a single &lt;code&gt;.NET Standard 2.0&lt;/code&gt; assembly; instead, we provide optimized binaries for .NET 10, 9, 8, 7, 6, 5, and .NET Standard 2.1 and 2.0.&lt;/p&gt;

&lt;p&gt;When you install the package, NuGet selects the &lt;code&gt;.NET 10&lt;/code&gt; binary, which is compiled with the latest JIT intrinsics and BCL (Base Class Library) improvements. This allows our codebase, which is rich with conditional compilation, to leverage the most powerful APIs available on each platform, including optimized &lt;code&gt;ReadOnlySpan&amp;lt;T&amp;gt;&lt;/code&gt;, &lt;code&gt;MemoryMarshal&lt;/code&gt;, and SIMD instructions for Base32 operations. This is key to our benchmark-leading speed.&lt;/p&gt;

&lt;h2&gt;
  
  
  The C# 14 &lt;code&gt;field&lt;/code&gt; Keyword: Eliminating Hot Path Overhead
&lt;/h2&gt;

&lt;p&gt;To ensure our ID generation is as fast as possible, we must eliminate all unnecessary overhead from the &lt;code&gt;Ulid.New()&lt;/code&gt; hot path.&lt;/p&gt;

&lt;p&gt;We use the new C# 14 &lt;code&gt;field&lt;/code&gt; keyword within our &lt;code&gt;GenerationOptions&lt;/code&gt; record for "fail-fast" validation. This design choice ensures that configuration validation runs only once when the configuration object is constructed, not inside the generation loop.&lt;/p&gt;

&lt;p&gt;For example, ensuring the &lt;code&gt;Monotonicity&lt;/code&gt; property is valid occurs during &lt;code&gt;init&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;MonotonicityOptions&lt;/span&gt; &lt;span class="n"&gt;Monotonicity&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;init&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;field&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Enum&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;IsDefined&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;MonotonicityOptions&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="k"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="k"&gt;value&lt;/span&gt;
        &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;ArgumentOutOfRangeException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="cm"&gt;/* ... */&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This single-time validation guarantees that any call to &lt;code&gt;Ulid.New()&lt;/code&gt; operates with a pre-validated, &lt;strong&gt;zero-overhead&lt;/strong&gt; configuration, a crucial optimization for high-volume systems.&lt;/p&gt;

&lt;h2&gt;
  
  
  Robustness: Programmatic Overflow Prevention
&lt;/h2&gt;

&lt;p&gt;Reliability is as vital as performance. A common, though often unaddressed, problem is the &lt;code&gt;OverflowException&lt;/code&gt; that can occur during rapid ULID generation within the same millisecond. This happens because the 80-bit random component can, by chance, start near its maximum value.&lt;/p&gt;

&lt;p&gt;Our library solves this programmatically: when a random component overflow is imminent, we &lt;strong&gt;intelligently increment the 48-bit timestamp component by 1ms&lt;/strong&gt;. This small, controlled time jump ensures &lt;strong&gt;continuous, error-free generation&lt;/strong&gt; while maintaining the lexicographical sort order.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;GenerationOptions&lt;/code&gt; also supports advanced security, allowing control over the &lt;code&gt;IRandomProvider&lt;/code&gt; and offering granular &lt;code&gt;MonotonicityOptions&lt;/code&gt; (like &lt;code&gt;MonotonicRandom1Byte&lt;/code&gt;) to defend against enumeration attacks.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;The 1.3.2 release of &lt;code&gt;ByteAether.Ulid&lt;/code&gt; provides .NET 10 support and leverages cutting-edge C# features to deliver a ULID implementation optimized for performance, security, and meticulous specification compliance.&lt;/p&gt;

&lt;p&gt;We encourage you to adopt &lt;code&gt;ByteAether.Ulid&lt;/code&gt; if your identifier generation strategy requires speed and robust design.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Read the full technical analysis and deep dive into the code on our blog.&lt;/strong&gt; &lt;a href="https://byteaether.github.io/2025/announcing-byteaetherulid-132-net-10-support-and-optimized-design/" rel="noopener noreferrer"&gt;https://byteaether.github.io/2025/announcing-byteaetherulid-132-net-10-support-and-optimized-design/&lt;/a&gt;&lt;/p&gt;

</description>
      <category>csharp</category>
      <category>dotnet</category>
      <category>webdev</category>
      <category>news</category>
    </item>
    <item>
      <title>Building a Secure DAL: Composable Multi-Tenancy Filtering with C# and Linq2Db</title>
      <dc:creator>GigAHerZ</dc:creator>
      <pubDate>Thu, 13 Nov 2025 14:00:00 +0000</pubDate>
      <link>https://dev.to/gigaherz/building-a-secure-dal-composable-multi-tenancy-filtering-with-c-and-linq2db-19lo</link>
      <guid>https://dev.to/gigaherz/building-a-secure-dal-composable-multi-tenancy-filtering-with-c-and-linq2db-19lo</guid>
      <description>&lt;p&gt;Securing a multi-tenant application means enforcing strict data isolation. This cannot be left to business logic, but it must be guaranteed at the Data Access Layer (DAL). We built upon our existing enterprise DAL foundation to implement automated, non-bypassable multi-tenancy filtering.&lt;/p&gt;

&lt;p&gt;This article summarizes the key architectural steps for senior engineers and architects looking to solve this critical cross-cutting concern.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem and Our Goal
&lt;/h2&gt;

&lt;p&gt;The goal is to ensure that every query executed against a tenant-aware entity is automatically filtered by the current tenant's ID. We need this filter to compose seamlessly with other existing global filters, such as soft-delete.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Passing Tenant Context to the DAL
&lt;/h2&gt;

&lt;p&gt;Our DAL must be &lt;strong&gt;tenant-aware&lt;/strong&gt;. We update our base database context interface, &lt;code&gt;IDbCtx&lt;/code&gt;, to hold request-scoped attributes, starting with the &lt;code&gt;TenantId&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;IDbCtx&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;IDataContext&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;DbCtxAttributes&lt;/span&gt; &lt;span class="n"&gt;Attributes&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;record&lt;/span&gt; &lt;span class="nc"&gt;DbCtxAttributes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Ulid&lt;/span&gt; &lt;span class="n"&gt;TenantId&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This attribute is initialized early in the request lifecycle, ensuring the current &lt;code&gt;TenantId&lt;/code&gt; is available for the database context's lifetime.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Defining the Tenancy Contract
&lt;/h2&gt;

&lt;p&gt;We use an &lt;code&gt;ITenanted&lt;/code&gt; interface to define the behavior and attach the filtering logic. The &lt;code&gt;[EntityFilter]&lt;/code&gt; attribute ties the interface to the static &lt;code&gt;Filter&lt;/code&gt; method, which contains the LINQ &lt;code&gt;Where&lt;/code&gt; clause.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;EntityFilter&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ITenanted&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="k"&gt;nameof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Filter&lt;/span&gt;&lt;span class="p"&gt;))]&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;ITenanted&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;IEntity&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Ulid&lt;/span&gt; &lt;span class="n"&gt;TenantId&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="n"&gt;IQueryable&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Filter&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="n"&gt;IQueryable&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;q&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;IDbCtx&lt;/span&gt; &lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;where&lt;/span&gt; &lt;span class="n"&gt;T&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ITenanted&lt;/span&gt;
        &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;q&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TenantId&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Attributes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TenantId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Our global filter system discovers this attribute and automatically applies this &lt;code&gt;Where&lt;/code&gt; clause to every query against implementing entities.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Solving Projected Tenancy
&lt;/h2&gt;

&lt;p&gt;Not all entities have a direct &lt;code&gt;tenant_id&lt;/code&gt; column. A &lt;code&gt;Post&lt;/code&gt;, for example, may inherit its tenancy from its parent &lt;code&gt;User&lt;/code&gt;. This is "Projected Tenancy."&lt;/p&gt;

&lt;p&gt;To handle this, we manually implement &lt;code&gt;ITenanted&lt;/code&gt; for the &lt;code&gt;Post&lt;/code&gt; entity and use the Linq2Db &lt;code&gt;[ExpressionMethod]&lt;/code&gt; attribute. This instructs Linq2Db to translate the property access into a SQL expression involving a join.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;partial&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Post&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ITenanted&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;ExpressionMethod&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;nameof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;GetTenantIdExpression&lt;/span&gt;&lt;span class="p"&gt;))]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;Ulid&lt;/span&gt; &lt;span class="n"&gt;TenantId&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;GetTenantIdExpression&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;Compile&lt;/span&gt;&lt;span class="p"&gt;()(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="n"&gt;Expression&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Func&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Post&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Ulid&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;GetTenantIdExpression&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;User&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TenantId&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Linq2Db parses the expression &lt;code&gt;x =&amp;gt; x.User.TenantId&lt;/code&gt;, generating an &lt;code&gt;INNER JOIN&lt;/code&gt; to the &lt;code&gt;user&lt;/code&gt; table and applying the tenant filter to the joined user's &lt;code&gt;tenant_id&lt;/code&gt; column.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion: Composable Security
&lt;/h2&gt;

&lt;p&gt;This architecture allows security rules to be defined once and enforced automatically. The generated SQL proves that soft-delete rules and multi-tenancy rules are correctly layered, guaranteeing both data integrity and isolation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;For the complete implementation, including scaffolding automation and the full generated SQL analysis, see the &lt;a href="https://byteaether.github.io/2025/building-an-enterprise-data-access-layer-composable-multi-tenancy-filtering/" rel="noopener noreferrer"&gt;full article on my blog&lt;/a&gt;.&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>csharp</category>
      <category>dotnet</category>
      <category>security</category>
      <category>architecture</category>
    </item>
    <item>
      <title>Stop manual soft-delete filtering. We built a composable architecture using Global Query Filters that automatically hides deleted records from all queries, even entity associations. Converts `DELETE` to auditable `UPDATE`. Read the full architecture.</title>
      <dc:creator>GigAHerZ</dc:creator>
      <pubDate>Tue, 11 Nov 2025 14:24:29 +0000</pubDate>
      <link>https://dev.to/gigaherz/stop-manual-soft-delete-filtering-we-built-a-composable-architecture-using-global-query-filters-1bp7</link>
      <guid>https://dev.to/gigaherz/stop-manual-soft-delete-filtering-we-built-a-composable-architecture-using-global-query-filters-1bp7</guid>
      <description>&lt;div class="ltag__link"&gt;
  &lt;a href="/gigaherz" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__pic"&gt;
      &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F2786169%2F3ac5be30-b6f3-4018-9747-5cb23c3f497a.jpg" alt="gigaherz"&gt;
    &lt;/div&gt;
  &lt;/a&gt;
  &lt;a href="https://dev.to/gigaherz/enterprise-data-access-fully-automated-soft-delete-a9b" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__content"&gt;
      &lt;h2&gt;Enterprise Data Access: Fully Automated Soft-Delete&lt;/h2&gt;
      &lt;h3&gt;GigAHerZ ・ Nov 6&lt;/h3&gt;
      &lt;div class="ltag__link__taglist"&gt;
        &lt;span class="ltag__link__tag"&gt;#database&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#architecture&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#dotnet&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#csharp&lt;/span&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
&lt;/div&gt;


</description>
      <category>database</category>
      <category>architecture</category>
      <category>dotnet</category>
      <category>csharp</category>
    </item>
    <item>
      <title>Enterprise Data Access: Fully Automated Soft-Delete</title>
      <dc:creator>GigAHerZ</dc:creator>
      <pubDate>Thu, 06 Nov 2025 14:00:00 +0000</pubDate>
      <link>https://dev.to/gigaherz/enterprise-data-access-fully-automated-soft-delete-a9b</link>
      <guid>https://dev.to/gigaherz/enterprise-data-access-fully-automated-soft-delete-a9b</guid>
      <description>&lt;p&gt;In professional software development, permanent data deletion is often a non-starter. Auditing, compliance, and recovery mandates require that we &lt;strong&gt;soft-delete&lt;/strong&gt; records, marking them as inactive rather than physically removing them.&lt;/p&gt;

&lt;p&gt;The implementation, however, is a common source of bugs and security vulnerabilities. A manual approach requiring developers to remember a &lt;code&gt;Where(x =&amp;gt; x.RemovedAt == null)&lt;/code&gt; clause on every query is simply untenable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Architectural Imperatives
&lt;/h2&gt;

&lt;p&gt;A production-grade soft-delete system must satisfy two non-negotiable requirements automatically:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Read Consistency:&lt;/strong&gt; Soft-deleted records must be filtered from every &lt;code&gt;SELECT&lt;/code&gt; query in the application.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Association Filtering:&lt;/strong&gt; The filter must propagate when querying entity associations. Loading a &lt;code&gt;Post&lt;/code&gt; must automatically exclude any soft-deleted &lt;code&gt;Comments&lt;/code&gt; from its collection.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;We achieved this by centralizing the logic using &lt;strong&gt;Global Query Filters&lt;/strong&gt; and engineering a &lt;strong&gt;composable filter architecture&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Composable Filter Solution
&lt;/h2&gt;

&lt;p&gt;A major limitation with many ORMs is the restriction to a single Global Query Filter per entity. Enterprise systems, however, require multiple cross-cutting concerns: soft-delete, multi-tenancy, and row-level security, each needing its own filter.&lt;/p&gt;

&lt;p&gt;Our solution is a system that aggregates multiple filter expressions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Behavior Definition:&lt;/strong&gt; An interface like &lt;code&gt;IRemovable&lt;/code&gt; defines the &lt;code&gt;RemovedAt&lt;/code&gt; property and uses a custom attribute to point to its static filtering method.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dynamic Composition:&lt;/strong&gt; At runtime, a process scans the entity hierarchy for all filtering attributes. It collects all individual filter lambdas (e.g., &lt;code&gt;x.RemovedAt == null&lt;/code&gt;, &lt;code&gt;x.TenantId == @CurrentTenant&lt;/code&gt;) and combines them using the logical &lt;strong&gt;AND&lt;/strong&gt; operator (&lt;code&gt;Expression.AndAlso&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Application:&lt;/strong&gt; The single, resulting aggregated expression is applied as the entity's Global Query Filter, ensuring all required conditions are met automatically on every query.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This system guarantees that any entity implementing &lt;code&gt;IRemovable&lt;/code&gt; is always filtered correctly, regardless of how or where it is queried.&lt;/p&gt;

&lt;h2&gt;
  
  
  Transparent Delete Interception
&lt;/h2&gt;

&lt;p&gt;To ensure data integrity and full auditability, we must intercept the &lt;code&gt;DELETE&lt;/code&gt; command.&lt;/p&gt;

&lt;p&gt;We implement new &lt;code&gt;RemoveAsync&lt;/code&gt; extension methods that check for the &lt;code&gt;IRemovable&lt;/code&gt; interface. When present, instead of executing a physical &lt;code&gt;DELETE&lt;/code&gt;, the DAL executes a secure &lt;code&gt;UPDATE&lt;/code&gt; statement that sets the &lt;code&gt;RemovedAt&lt;/code&gt; timestamp.&lt;/p&gt;

&lt;p&gt;This not only performs the soft-delete but, by chaining into our existing data modification logic, also automatically updates the &lt;code&gt;ModifiedAt&lt;/code&gt; timestamp, giving us a complete audit trail for the "deletion."&lt;/p&gt;

&lt;p&gt;The output SQL is a confirmation of success: the hard delete is converted into an auditable update, and the global filter is even applied to the update query itself to prevent re-deleting an already soft-deleted record.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion and Next Steps
&lt;/h2&gt;

&lt;p&gt;By moving soft-delete from an application concern to a core DAL feature, we have eliminated a significant source of developer error and enhanced system security and consistency. The composable filter system is the key takeaway, providing a pattern for managing multiple cross-cutting data concerns simultaneously.&lt;/p&gt;

&lt;p&gt;This foundational architecture is now being leveraged to implement our next critical feature: &lt;strong&gt;multi-tenancy&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;For the full source code and complete architectural deep dive, visit the &lt;a href="https://byteaether.github.io/2025/building-an-enterprise-data-access-layer-automated-soft-delete/" rel="noopener noreferrer"&gt;original article on my blog&lt;/a&gt;.&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>database</category>
      <category>architecture</category>
      <category>dotnet</category>
      <category>csharp</category>
    </item>
  </channel>
</rss>
