<?xml version="1.0" encoding="utf-8"?> <feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en"><title>ByteAether</title><subtitle>Decoding complexity to craft beautiful simplicity.</subtitle><link href="https://byteaether.github.io/feed/feed.xml" rel="self"/><link href="https://byteaether.github.io/"/><updated>2026-04-02T00:00:00Z</updated><id>https://byteaether.github.io/</id><author><name>Joonatan Uusväli</name></author><entry><title>The Architecture of Obsession: Why ByteAether.Ulid is the Last Identifier Library You Will Ever Need</title><link href="https://byteaether.github.io/2026/the-architecture-of-obsession-why-byteaetherulid-is-the-last-identifier-library-you-will-ever-need/"/><updated>2026-04-02T00:00:00Z</updated><id>https://byteaether.github.io/2026/the-architecture-of-obsession-why-byteaetherulid-is-the-last-identifier-library-you-will-ever-need/</id><content type="html">&lt;p&gt;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, &lt;code&gt;Guid.NewGuid()&lt;/code&gt; 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, &amp;quot;good enough&amp;quot; is a ticking time bomb of index fragmentation and out-of-order writes.&lt;/p&gt;&lt;p&gt;I have spent an unreasonable amount of time obsessing over &lt;a href=&quot;https://github.com/ulid/spec&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Universally Unique Lexicographically Sortable Identifiers (ULIDs)&lt;/a&gt;. While the rest of the world moved on to other things, I stayed behind to build &lt;strong&gt;&lt;a href=&quot;https://github.com/ByteAether/Ulid&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;ByteAether.Ulid&lt;/a&gt;&lt;/strong&gt;, a library that is, quite frankly, over-engineered to the point of insanity. But in a world of &amp;quot;lazy&amp;quot; implementations and optional monotonicity, perhaps a bit of insanity is exactly what your distributed system needs.&lt;/p&gt;&lt;h2 id=&quot;the-sortability%3A-why-uuidv7-isn&#39;t-the-hero-we-expected&quot; tabindex=&quot;-1&quot;&gt;The Sortability: Why UUIDv7 Isn&#39;t the Hero We Expected&lt;/h2&gt;&lt;p&gt;For a long time, the industry waited for a standardized, sortable replacement for the legacy GUID (UUIDv4). When UUIDv7 arrived via &lt;a href=&quot;https://www.rfc-editor.org/rfc/rfc9562#name-monotonicity-and-counters&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;RFC 9562&lt;/a&gt;, it seemed like the answer. Even .NET introduced a native provider for it. However, if you dig into the implementation details (specifically how the &lt;a href=&quot;https://github.com/dotnet/runtime/blob/571b044582ceb7fe426b7f143c703064aa9ea4db/src/libraries/System.Private.CoreLib/src/System/Guid.cs#L306&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;native .NET provider implements the generation of UUIDv7&lt;/a&gt;), you find a compromise.&lt;/p&gt;&lt;p&gt;The specification for UUIDv7 makes monotonicity &lt;strong&gt;optional&lt;/strong&gt;. 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.&lt;/p&gt;&lt;p&gt;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 &amp;quot;lazy&amp;quot; path.&lt;/p&gt;&lt;h2 id=&quot;eliminating-the-overflow-exception&quot; tabindex=&quot;-1&quot;&gt;Eliminating the Overflow Exception&lt;/h2&gt;&lt;p&gt;One of the &lt;a href=&quot;https://github.com/ulid/spec?tab=readme-ov-file#monotonicity&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;darker corners of the ULID specification&lt;/a&gt; involves what happens when the 80-bit random component overflows during a monotonic increment within the same millisecond. Many libraries simply throw an &lt;code&gt;OverflowException&lt;/code&gt; (as specification dictates), effectively crashing your ingestion pipeline because you were &amp;quot;too fast&amp;quot; for the generator. This isn&#39;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.&lt;/p&gt;&lt;p&gt;In &lt;a href=&quot;https://github.com/ByteAether/Ulid&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;ByteAether.Ulid&lt;/a&gt;, 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 &lt;a href=&quot;https://byteaether.github.io/2025/prioritizing-reliability-when-milliseconds-arent-enough/&quot;&gt;Handling ULID Overflows Gracefully&lt;/a&gt;.&lt;/p&gt;&lt;h2 id=&quot;high-performance%2C-lock-free-concurrency&quot; tabindex=&quot;-1&quot;&gt;High-Performance, Lock-Free Concurrency&lt;/h2&gt;&lt;p&gt;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.&lt;/p&gt;&lt;p&gt;ByteAether.Ulid utilizes a &lt;strong&gt;lock-free compare-and-exchange (CAS)&lt;/strong&gt; 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.&lt;/p&gt;&lt;p&gt;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 &lt;a href=&quot;https://github.com/ByteAether/Ulid?tab=readme-ov-file#benchmarking&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;GitHub page&lt;/a&gt;.&lt;/p&gt;&lt;h2 id=&quot;preventing-enumeration-attacks-with-random-increments&quot; tabindex=&quot;-1&quot;&gt;Preventing Enumeration Attacks with Random Increments&lt;/h2&gt;&lt;p&gt;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 &lt;a href=&quot;https://github.com/ulid/spec/issues/105&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;a significant security risk&lt;/a&gt; for public-facing APIs.&lt;/p&gt;&lt;p&gt;To mitigate this, I have implemented configurable random increments. Instead of adding a static &lt;code&gt;1&lt;/code&gt; 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.&lt;/p&gt;&lt;h2 id=&quot;the-developer-experience%3A-frictionless-integration&quot; tabindex=&quot;-1&quot;&gt;The Developer Experience: Frictionless Integration&lt;/h2&gt;&lt;p&gt;A library should feel like a native part of your stack, not an obstacle. I&#39;ve focused on ensuring that &lt;strong&gt;ByteAether.Ulid&lt;/strong&gt; integrates seamlessly with modern .NET defaults while providing clear pathways for specialized tooling. The library includes native, built-in support for:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;System.Text.Json:&lt;/strong&gt; Includes a high-performance &lt;code&gt;JsonConverter&lt;/code&gt; for effortless serialization to canonical Base32 strings.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;ASP.NET Core:&lt;/strong&gt; Built-in &lt;code&gt;TypeConverter&lt;/code&gt; support allows you to use &lt;code&gt;Ulid&lt;/code&gt; directly as a route or query parameter in your controllers and Minimal APIs.&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;For the rest of the ecosystem, I&#39;ve ensured the implementation is as &amp;quot;plug-and-play&amp;quot; as possible. Whether you are using &lt;strong&gt;Entity Framework Core&lt;/strong&gt;, &lt;strong&gt;Dapper&lt;/strong&gt;, &lt;strong&gt;Newtonsoft.Json&lt;/strong&gt;, or &lt;strong&gt;MessagePack&lt;/strong&gt;, the library&#39;s architecture makes integration trivial. I&#39;ve documented the exact, low-boilerplate snippets required for these tools in the &lt;a href=&quot;https://github.com/ByteAether/Ulid?tab=readme-ov-file#integration-with-other-libraries&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;GitHub README&lt;/a&gt;, so you can drop them into your project and get back to building your core logic.&lt;/p&gt;&lt;h3 id=&quot;advanced-filtering-with-linq&quot; tabindex=&quot;-1&quot;&gt;Advanced Filtering with LINQ&lt;/h3&gt;&lt;p&gt;One of the most powerful features of ULIDs is the ability to perform time-range queries without a separate &lt;code&gt;CreatedAt&lt;/code&gt; column. Because the timestamp is embedded in the ID, you can generate boundary ULIDs:&lt;/p&gt;&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code class=&quot;language-csharp&quot;&gt;&lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt;&lt;/span&gt; start &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Ulid&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;MinAt&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;DateTimeOffset&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;UtcNow&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;AddDays&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;7&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt;&lt;/span&gt; end &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Ulid&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;MaxAt&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;DateTimeOffset&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;UtcNow&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// EF Core translates this into an efficient range comparison on the Primary Key&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt;&lt;/span&gt; reports &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; context&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Reports &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Where&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;r &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; r&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Id &lt;span class=&quot;token operator&quot;&gt;&gt;=&lt;/span&gt; start &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; r&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Id &lt;span class=&quot;token operator&quot;&gt;&amp;lt;=&lt;/span&gt; end&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;ToListAsync&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This approach allows you to leverage the primary key index for high-performance temporal queries, reducing the need for additional composite indexes.&lt;/p&gt;&lt;h2 id=&quot;embracing-the-over-engineered&quot; tabindex=&quot;-1&quot;&gt;Embracing the Over-Engineered&lt;/h2&gt;&lt;p&gt;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.&lt;/p&gt;&lt;p&gt;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 &amp;quot;insane&amp;quot; enough to build this.&lt;/p&gt;&lt;p&gt;Stop settling for &amp;quot;lazy&amp;quot; identifiers. Give your architecture the precision it deserves by checking out the project on &lt;a href=&quot;https://github.com/ByteAether/Ulid&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;GitHub&lt;/a&gt;.&lt;/p&gt;</content></entry><entry><title>The Weight of Decisions: Solving Weighted Random Sorting at Scale</title><link href="https://byteaether.github.io/2026/the-weight-of-decisions-solving-weighted-random-sorting-at-scale/"/><updated>2026-01-07T00:00:00Z</updated><id>https://byteaether.github.io/2026/the-weight-of-decisions-solving-weighted-random-sorting-at-scale/</id><content type="html">&lt;p&gt;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.&lt;/p&gt;&lt;p&gt;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.&lt;/p&gt;&lt;h2 id=&quot;the-requirement-for-ordered-fail-over&quot; tabindex=&quot;-1&quot;&gt;The Requirement for Ordered Fail-over&lt;/h2&gt;&lt;p&gt;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 &amp;quot;fail-over&amp;quot; list for every request. If the primary choice failed, the system needed an immediate second and third option ready to go.&lt;/p&gt;&lt;p&gt;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.&lt;/p&gt;&lt;h2 id=&quot;balancing-traffic-via-weighted-randomization&quot; tabindex=&quot;-1&quot;&gt;Balancing Traffic via Weighted Randomization&lt;/h2&gt;&lt;p&gt;A simple deterministic sort based on quality scores would create a &amp;quot;starvation&amp;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 &amp;quot;exploration&amp;quot; traffic to lower-ranked services, we could never observe if their performance had recovered or improved.&lt;/p&gt;&lt;p&gt;To maintain fresh statistics across the entire pool, we needed a weighted random sort, effectively a &lt;strong&gt;weighted permutation&lt;/strong&gt;. Consider a scenario with three services and the following target distributions:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;Service A:&lt;/strong&gt; 50%&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Service B:&lt;/strong&gt; 30%&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Service C:&lt;/strong&gt; 20%&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;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.&lt;/p&gt;&lt;h2 id=&quot;implementation-logic&quot; tabindex=&quot;-1&quot;&gt;Implementation Logic&lt;/h2&gt;&lt;p&gt;Both the initial and the improved versions of our code use the following data structure to represent a destination:&lt;/p&gt;&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code class=&quot;language-csharp&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Item&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token return-type class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;string&lt;/span&gt;&lt;/span&gt; Name &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token return-type class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;double&lt;/span&gt;&lt;/span&gt; Weight &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&quot;the-intuition-and-the-initial-o(n2-log-n)-approach&quot; tabindex=&quot;-1&quot;&gt;The Intuition and the Initial O(N&lt;sup&gt;2&lt;/sup&gt; log N) Approach&lt;/h2&gt;&lt;p&gt;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.&lt;/p&gt;&lt;p&gt;In C#, the implementation relied on a &amp;quot;weighted pick and remove&amp;quot; loop. The computational cost was actually O(N&lt;sup&gt;2&lt;/sup&gt; 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.&lt;/p&gt;&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code class=&quot;language-csharp&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;token return-type class-name&quot;&gt;List&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;T&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token generic-method&quot;&gt;&lt;span class=&quot;token function&quot;&gt;InefficientWeightedRandomize&lt;/span&gt;&lt;span class=&quot;token generic class-name&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;T&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;IEnumerable&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;T&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; source&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;where&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;T&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token type-list&quot;&gt;&lt;span class=&quot;token class-name&quot;&gt;Item&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt;&lt;/span&gt; remainingItems &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; source&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;ToList&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt;&lt;/span&gt; result &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token constructor-invocation class-name&quot;&gt;List&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;T&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;while&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;remainingItems&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Any&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// Sort the source list by a random factor modified by weight&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt;&lt;/span&gt; nextItem &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; remainingItems &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Select&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;item &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; Item &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; item&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; SortKey &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Random&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Shared&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;NextDouble&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; item&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Weight &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;OrderByDescending&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;x &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; x&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;SortKey&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;First&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; result&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Add&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;nextItem&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Item&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; remainingItems&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Remove&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;nextItem&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Item&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; result&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&quot;the-efraimidis-spirakis-algorithm&quot; tabindex=&quot;-1&quot;&gt;The Efraimidis-Spirakis Algorithm&lt;/h2&gt;&lt;p&gt;Recently, I came across the &lt;a href=&quot;https://utopia.duth.gr/~pefraimi/research/data/2007EncOfAlg.pdf&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Efraimidis-Spirakis Weighted Random Sampling paper&lt;/a&gt;, which provides the mathematical proof for the &amp;quot;one-pass sort&amp;quot; I had originally envisioned. The paper demonstrates that calculating a key k&lt;sub&gt;i&lt;/sub&gt; = u&lt;sub&gt;i&lt;/sub&gt;&lt;sup&gt;1/w&lt;sub&gt;i&lt;/sub&gt;&lt;/sup&gt; (where &lt;code&gt;u&lt;/code&gt; is a random value and &lt;code&gt;w&lt;/code&gt; is the weight) and sorting by that key produces a mathematically perfect weighted random sample.&lt;/p&gt;&lt;p&gt;This technique is a specialized form of &lt;strong&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Reservoir_sampling&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Reservoir Sampling&lt;/a&gt;&lt;/strong&gt;. It allows us to process the list in O(N log N) time with a much cleaner implementation:&lt;/p&gt;&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code class=&quot;language-csharp&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;token return-type class-name&quot;&gt;List&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;T&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token generic-method&quot;&gt;&lt;span class=&quot;token function&quot;&gt;WeightedRandomize&lt;/span&gt;&lt;span class=&quot;token generic class-name&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;T&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;IEnumerable&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;T&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; source&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;where&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;T&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token type-list&quot;&gt;&lt;span class=&quot;token class-name&quot;&gt;Item&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; source &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Select&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;item &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; Item &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; item&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; SortKey &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Math&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Pow&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt; Random&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Shared&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;NextDouble&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1.0&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt; item&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Weight &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;OrderByDescending&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;x &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; x&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;SortKey&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Select&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;x &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; x&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Item&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;ToList&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&quot;applications-for-streaming-data&quot; tabindex=&quot;-1&quot;&gt;Applications for Streaming Data&lt;/h2&gt;&lt;p&gt;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&#39;s &lt;code&gt;SortKey&lt;/code&gt; 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 &amp;quot;Top K&amp;quot; selection) yields the result.&lt;/p&gt;&lt;p&gt;For the team back at Microsoft still managing these services, this is a small but elegant improvement. While N = 10 doesn&#39;t demand high-performance algorithms, replacing the O(N&lt;sup&gt;2&lt;/sup&gt;) 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.&lt;/p&gt;</content></entry><entry><title>Building an Enterprise Data Access Layer: Automated User Auditing and Series Wrap-up</title><link href="https://byteaether.github.io/2025/building-an-enterprise-data-access-layer-automated-user-auditing-and-series-wrap-up/"/><updated>2025-12-01T00:00:00Z</updated><id>https://byteaether.github.io/2025/building-an-enterprise-data-access-layer-automated-user-auditing-and-series-wrap-up/</id><content type="html">&lt;p&gt;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 Linq2Db.&lt;/p&gt;&lt;p&gt;We&#39;ve tackled soft-deletes, timestamp auditing, multi-tenancy, and even complex projected row-level security. In our &lt;a href=&quot;https://byteaether.github.io/2025/building-an-enterprise-data-access-layer-the-foundation/&quot;&gt;very first article&lt;/a&gt;, we set a high bar for the capabilities our DAL must provide. Today, we implement the final missing piece: &lt;strong&gt;automated user auditing&lt;/strong&gt; (&lt;code&gt;CreatedByUser&lt;/code&gt; / &lt;code&gt;ModifiedByUser&lt;/code&gt;).&lt;/p&gt;&lt;p&gt;Once that&#39;s in place, we&#39;ll hold a full retrospective to see how our final architecture stacks up against our original goals, and finally, we&#39;ll look at what challenges lie beyond this series.&lt;/p&gt;&lt;h2 id=&quot;implementing-user-auditing&quot; tabindex=&quot;-1&quot;&gt;Implementing User Auditing&lt;/h2&gt;&lt;p&gt;So far, our audit fields (&lt;code&gt;CreatedAt&lt;/code&gt;, &lt;code&gt;ModifiedAt&lt;/code&gt;) tell us &lt;em&gt;when&lt;/em&gt; a record was changed. User auditing completes the picture by telling us &lt;em&gt;who&lt;/em&gt; made that change. This is a common requirement for compliance, traceability, and debugging.&lt;/p&gt;&lt;p&gt;Our goal is to automatically populate &lt;code&gt;CreatedByUserId&lt;/code&gt; on insert and &lt;code&gt;ModifiedByUserId&lt;/code&gt; on insert and update, using the &lt;code&gt;UserId&lt;/code&gt; from our request-scoped context. We will follow the exact same pattern of &amp;quot;behavioral interfaces&amp;quot; that we established in &lt;a href=&quot;https://byteaether.github.io/2025/building-an-enterprise-data-access-layer-automated-auditing/&quot;&gt;Part 3 (Auditing)&lt;/a&gt; and &lt;a href=&quot;https://byteaether.github.io/2025/building-an-enterprise-data-access-layer-automated-soft-delete/&quot;&gt;Part 4 (Soft-Delete)&lt;/a&gt;.&lt;/p&gt;&lt;h3 id=&quot;the-schema-and-interface-contracts&quot; tabindex=&quot;-1&quot;&gt;The Schema and Interface Contracts&lt;/h3&gt;&lt;p&gt;Following our database-first approach, we first update the database schema. We add &lt;code&gt;created_by_user_id ulid not null&lt;/code&gt; and &lt;code&gt;modified_by_user_id ulid not null&lt;/code&gt; columns to the &lt;code&gt;post&lt;/code&gt; and &lt;code&gt;comment&lt;/code&gt; tables, both with foreign keys referencing the &lt;code&gt;user&lt;/code&gt; table.&lt;/p&gt;&lt;p&gt;Next, we define the C# contracts in &lt;code&gt;DAL.Base/EntityBehavior/&lt;/code&gt;:&lt;/p&gt;&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code class=&quot;language-csharp&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;namespace&lt;/span&gt; &lt;span class=&quot;token namespace&quot;&gt;DAL&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Base&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;EntityBehavior&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;interface&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;IUserCreatable&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token return-type class-name&quot;&gt;Ulid&lt;/span&gt; CreatedByUserId &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code class=&quot;language-csharp&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;namespace&lt;/span&gt; &lt;span class=&quot;token namespace&quot;&gt;DAL&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Base&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;EntityBehavior&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;interface&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;IUserModifiable&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token return-type class-name&quot;&gt;Ulid&lt;/span&gt; ModifiedByUserId &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;These simple interfaces define the properties our DAL will automatically manage.&lt;/p&gt;&lt;h3 id=&quot;automated-scaffolding&quot; tabindex=&quot;-1&quot;&gt;Automated Scaffolding&lt;/h3&gt;&lt;p&gt;With the database and interfaces in place, we update our custom scaffolding interceptor (&lt;code&gt;DAL.ScaffoldInterceptor/Interceptor.cs&lt;/code&gt;).&lt;/p&gt;&lt;p&gt;Just as it was configured to detect &lt;code&gt;created_at&lt;/code&gt; and attach &lt;code&gt;ICreatable&lt;/code&gt;, we&#39;ve now taught it to look for &lt;code&gt;created_by_user_id&lt;/code&gt; and &lt;code&gt;modified_by_user_id&lt;/code&gt; columns. When it finds them, it automatically adds &lt;code&gt;IUserCreatable&lt;/code&gt; and &lt;code&gt;IUserModifiable&lt;/code&gt; to the generated partial entity classes in &lt;code&gt;DbCtx.generated.cs&lt;/code&gt;.&lt;/p&gt;&lt;p&gt;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&#39;s needed to make the C# code aware of this new behavior.&lt;/p&gt;&lt;h3 id=&quot;updating-the-crud-extensions&quot; tabindex=&quot;-1&quot;&gt;Updating the CRUD Extensions&lt;/h3&gt;&lt;p&gt;The final step is to update our &lt;code&gt;CrudExtensions.cs&lt;/code&gt; file to enforce the logic. We need to fetch the current user&#39;s ID from our &lt;code&gt;IDbCtx&lt;/code&gt; (which we added in &lt;a href=&quot;https://byteaether.github.io/2025/building-an-enterprise-data-access-layer-composable-row-level-security/&quot;&gt;Part 6&lt;/a&gt;) and assign it.&lt;/p&gt;&lt;p&gt;We update &lt;code&gt;CreateAsync&lt;/code&gt;, &lt;code&gt;ModifyAsync&lt;/code&gt; (for single entity), and &lt;code&gt;ModifyAsync&lt;/code&gt; (for fluent queries) to handle these new interfaces.&lt;/p&gt;&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code class=&quot;language-csharp&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token return-type class-name&quot;&gt;Task&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;long&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token generic-method&quot;&gt;&lt;span class=&quot;token function&quot;&gt;CreateAsync&lt;/span&gt;&lt;span class=&quot;token generic class-name&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;T&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;IDbCtx&lt;/span&gt; ctx&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;IEnumerable&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;T&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; entities&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;CancellationToken&lt;/span&gt; cancellationToken &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;where&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;T&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token type-list&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;IEntity&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; ctx&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;BulkCopyAsync&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt; entities&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Select&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;e &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// Timestamp Auditing (from Part 3)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt; e &lt;span class=&quot;token keyword&quot;&gt;is&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ICreatable&lt;/span&gt; creatable &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; creatable&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;CreatedAt &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; creatable&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;CreatedAt &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; DateTime&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;UtcNow&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// NEW: User Create Auditing&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt; e &lt;span class=&quot;token keyword&quot;&gt;is&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;IUserCreatable&lt;/span&gt; userCreatable &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; userCreatable&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;CreatedByUserId &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; userCreatable&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;CreatedByUserId &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; ctx&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Attributes&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;UserId &lt;span class=&quot;token operator&quot;&gt;??&lt;/span&gt; Ulid&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Empty&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// Timestamp Auditing (from Part 3)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt; e &lt;span class=&quot;token keyword&quot;&gt;is&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;IModifiable&lt;/span&gt; updateable &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; updateable&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ModifiedAt &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; updateable&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ModifiedAt &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; DateTime&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;UtcNow&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// NEW: User Modify Auditing&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt; e &lt;span class=&quot;token keyword&quot;&gt;is&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;IUserModifiable&lt;/span&gt; userUpdateable &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; userUpdateable&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ModifiedByUserId &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; userUpdateable&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ModifiedByUserId &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; ctx&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Attributes&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;UserId &lt;span class=&quot;token operator&quot;&gt;??&lt;/span&gt; Ulid&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Empty&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; e&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; cancellationToken &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;RowsCopied&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;token return-type class-name&quot;&gt;Task&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token generic-method&quot;&gt;&lt;span class=&quot;token function&quot;&gt;ModifyAsync&lt;/span&gt;&lt;span class=&quot;token generic class-name&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;T&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;IDbCtx&lt;/span&gt; ctx&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;T&lt;/span&gt; entity&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;CancellationToken&lt;/span&gt; cancellationToken &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;where&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;T&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token type-list&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;IEntity&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// Timestamp Auditing (from Part 3)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;entity &lt;span class=&quot;token keyword&quot;&gt;is&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;IModifiable&lt;/span&gt; updateable&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; updateable&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ModifiedAt &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; DateTime&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;UtcNow&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// NEW: User Modify Auditing&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;entity &lt;span class=&quot;token keyword&quot;&gt;is&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;IUserModifiable&lt;/span&gt; userUpdateable&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; userUpdateable&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ModifiedByUserId &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; ctx&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Attributes&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;UserId &lt;span class=&quot;token operator&quot;&gt;??&lt;/span&gt; Ulid&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Empty&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; ctx&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token generic-method&quot;&gt;&lt;span class=&quot;token function&quot;&gt;GetTable&lt;/span&gt;&lt;span class=&quot;token generic class-name&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;T&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;UpdateOptimisticAsync&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;entity&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; cancellationToken&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;h3 id=&quot;the-iupdatable-nuance&quot; tabindex=&quot;-1&quot;&gt;The &lt;code&gt;IUpdatable&lt;/code&gt; Nuance&lt;/h3&gt;&lt;p&gt;The fluent &lt;code&gt;ModifyAsync&lt;/code&gt; extension (which operates on &lt;code&gt;IUpdatable&amp;lt;T&amp;gt;&lt;/code&gt;) presents a unique challenge. This method is what allows us to write code like &lt;code&gt;ctx.GetTable&amp;lt;Post&amp;gt;().Where(p =&amp;gt; p.Id == id).ModifyAsync()&lt;/code&gt;.&lt;/p&gt;&lt;p&gt;To inject the &lt;code&gt;ModifiedByUserId&lt;/code&gt; value, we need the &lt;code&gt;IDbCtx&lt;/code&gt; instance to get the &lt;code&gt;ctx.Attributes.UserId&lt;/code&gt;. However, the &lt;code&gt;IUpdatable&amp;lt;T&amp;gt;&lt;/code&gt; (which is essentially an &lt;code&gt;IQueryable&lt;/code&gt;) doesn&#39;t expose its parent context directly.&lt;/p&gt;&lt;p&gt;To solve this, we use a helper provided by Linq2Db:&lt;/p&gt;&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code class=&quot;language-csharp&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;token return-type class-name&quot;&gt;Task&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token generic-method&quot;&gt;&lt;span class=&quot;token function&quot;&gt;ModifyAsync&lt;/span&gt;&lt;span class=&quot;token generic class-name&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;T&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;IUpdatable&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;T&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; source&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;CancellationToken&lt;/span&gt; cancellationToken &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;where&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;T&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token type-list&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// Timestamp Auditing (from Part 3)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;typeof&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token type-expression class-name&quot;&gt;IModifiable&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;IsAssignableFrom&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;typeof&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token type-expression class-name&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; source &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; source&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Set&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt; x &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; Sql&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token generic-method&quot;&gt;&lt;span class=&quot;token function&quot;&gt;Property&lt;/span&gt;&lt;span class=&quot;token generic class-name&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;DateTime&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;x&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;nameof&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;IModifiable&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ModifiedAt&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; DateTime&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;UtcNow &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// NEW: User Modify Auditing&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;typeof&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token type-expression class-name&quot;&gt;IUserModifiable&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;IsAssignableFrom&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;typeof&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token type-expression class-name&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// Get the DbCtx from the query&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt;&lt;/span&gt; dbCtx &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Internals&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;GetDataContext&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;source&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;IDbCtx&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; source &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; source&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Set&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt; x &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; Sql&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token generic-method&quot;&gt;&lt;span class=&quot;token function&quot;&gt;Property&lt;/span&gt;&lt;span class=&quot;token generic class-name&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;Ulid&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;x&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;nameof&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;IUserModifiable&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ModifiedByUserId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; dbCtx&lt;span class=&quot;token punctuation&quot;&gt;?.&lt;/span&gt;Attributes&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;UserId &lt;span class=&quot;token operator&quot;&gt;??&lt;/span&gt; Ulid&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Empty &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; source&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;UpdateAsync&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;cancellationToken&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The key part is &lt;code&gt;(LinqToDB.Internal.Linq.)Internals.GetDataContext(source)&lt;/code&gt;. While using an &lt;code&gt;Internal&lt;/code&gt; 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 &lt;code&gt;UPDATE&lt;/code&gt; operations.&lt;/p&gt;&lt;p&gt;And with that, our final automated behavior is implemented.&lt;/p&gt;&lt;h3 id=&quot;the-result%3A-a-fully-automated-query&quot; tabindex=&quot;-1&quot;&gt;The Result: A Fully Automated Query&lt;/h3&gt;&lt;p&gt;To see all our work come together, let&#39;s look at the simple &lt;code&gt;ModifyAsync&lt;/code&gt; call from our &lt;code&gt;Program.cs&lt;/code&gt; that updates a &lt;code&gt;Comment&lt;/code&gt;:&lt;/p&gt;&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code class=&quot;language-csharp&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// Try to update a protected comment entity&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; ctx&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token generic-method&quot;&gt;&lt;span class=&quot;token function&quot;&gt;GetTable&lt;/span&gt;&lt;span class=&quot;token generic class-name&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;Comment&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Set&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;x &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; x&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Content&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;asd123&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;ModifyAsync&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;When this C# code executes, our DAL intercepts it and, using all the logic from this series, generates the following comprehensive SQL query:&lt;/p&gt;&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;UPDATE&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;comment&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;SET&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;content&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;asd123&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;modified_at&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;2025-11-12 15:31:11.972&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;modified_by_user_id&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; X&lt;span class=&quot;token string&quot;&gt;&#39;019A78B1378CC2EC0F1736EE8D4F1E8F&#39;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;post&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;a_Post&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;a_User&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;permission&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;b&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;WHERE&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;a_User&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;removed_at&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;IS&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;NULL&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;AND&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;a_User&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;tenant_id&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; X&lt;span class=&quot;token string&quot;&gt;&#39;019A78B13787E6060BF9BA9A51DAA2C6&#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;AND&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;a_Post&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;removed_at&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;IS&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;NULL&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;AND&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;comment&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;removed_at&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;IS&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;NULL&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;AND&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;comment&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;post_id&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;a_Post&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;id&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;AND&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;a_Post&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;user_id&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;a_User&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;id&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;AND&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;b&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;object_id&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;a_Post&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;id&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;AND&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;b&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;subject_id&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; X&lt;span class=&quot;token string&quot;&gt;&#39;019A78B1378CC2EC0F1736EE8D4F1E8F&#39;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This single &lt;code&gt;UPDATE&lt;/code&gt; statement perfectly demonstrates our entire architecture working in harmony. The &lt;code&gt;SET&lt;/code&gt; clause not only updates the &lt;code&gt;content&lt;/code&gt; but also automatically injects &lt;strong&gt;&lt;code&gt;modified_at&lt;/code&gt;&lt;/strong&gt; (from &lt;a href=&quot;https://byteaether.github.io/2025/building-an-enterprise-data-access-layer-automated-auditing/&quot;&gt;Part 3&lt;/a&gt;) and our new &lt;strong&gt;&lt;code&gt;modified_by_user_id&lt;/code&gt;&lt;/strong&gt; field.&lt;/p&gt;&lt;p&gt;Furthermore, the &lt;code&gt;WHERE&lt;/code&gt; clause is a composition of all our global filters:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;Soft-Delete (&lt;code&gt;removed_at IS NULL&lt;/code&gt;)&lt;/strong&gt; on &lt;code&gt;user&lt;/code&gt;, &lt;code&gt;post&lt;/code&gt;, and &lt;code&gt;comment&lt;/code&gt;.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Multi-Tenancy (&lt;code&gt;tenant_id = ...&lt;/code&gt;)&lt;/strong&gt; on the &lt;code&gt;user&lt;/code&gt; table (a projected filter).&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Row-Level Security (&lt;code&gt;[b].[object_id] = ...&lt;/code&gt;)&lt;/strong&gt; by joining the &lt;code&gt;permission&lt;/code&gt; table (another projected filter).&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;This is the automation we set out to build, all abstracted from the developer who just called &lt;code&gt;.ModifyAsync()&lt;/code&gt;.&lt;/p&gt;&lt;h2 id=&quot;achieving-our-goals&quot; tabindex=&quot;-1&quot;&gt;Achieving Our Goals&lt;/h2&gt;&lt;p&gt;In our &lt;a href=&quot;https://byteaether.github.io/2025/building-an-enterprise-data-access-layer-the-foundation/&quot;&gt;first article&lt;/a&gt;, 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&#39;s review our original checklist.&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;Soft-Delete:&lt;/strong&gt;&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;Goal:&lt;/strong&gt; Automatically filter out records marked with a &lt;code&gt;DeletedAt&lt;/code&gt; timestamp on all read operations.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Achieved:&lt;/strong&gt; Yes. In &lt;a href=&quot;https://byteaether.github.io/2025/building-an-enterprise-data-access-layer-automated-soft-delete/&quot;&gt;Part 4&lt;/a&gt;, we implemented &lt;code&gt;IRemovable&lt;/code&gt; and a composable global query filter system. This system automatically and ubiquitously applies the &lt;code&gt;WHERE [removed_at] IS NULL&lt;/code&gt; clause to all queries, including joins. We also overrode &lt;code&gt;DeleteAsync&lt;/code&gt; to transparently convert deletes into updates.&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;Timestamp Auditing:&lt;/strong&gt;&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;Goal:&lt;/strong&gt; Automatically populate &lt;code&gt;CreatedAt&lt;/code&gt; and &lt;code&gt;ModifiedAt&lt;/code&gt; timestamps.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Achieved:&lt;/strong&gt; Yes. In &lt;a href=&quot;https://byteaether.github.io/2025/building-an-enterprise-data-access-layer-automated-auditing/&quot;&gt;Part 3&lt;/a&gt;, we used &lt;code&gt;ICreatable&lt;/code&gt; and &lt;code&gt;IModifiable&lt;/code&gt; interfaces and &lt;code&gt;CrudExtensions&lt;/code&gt; to inject the current timestamp during create and modify operations.&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;User Auditing:&lt;/strong&gt;&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;Goal:&lt;/strong&gt; Automatically populate &lt;code&gt;CreatedByUserId&lt;/code&gt; and &lt;code&gt;ModifiedByUserId&lt;/code&gt; with the current user&#39;s ID.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Achieved:&lt;/strong&gt; Yes. We just completed this in the first section of this article, using the exact same robust pattern (&lt;code&gt;IUserCreatable&lt;/code&gt;, &lt;code&gt;IUserModifiable&lt;/code&gt;).&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;Multi-Tenancy Filtering:&lt;/strong&gt;&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;Goal:&lt;/strong&gt; Automatically inject a &lt;code&gt;WHERE TenantId = [CurrentTenantId]&lt;/code&gt; clause for all tenant-owned entities.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Achieved:&lt;/strong&gt; Yes. In &lt;a href=&quot;https://byteaether.github.io/2025/building-an-enterprise-data-access-layer-composable-multi-tenancy-filtering/&quot;&gt;Part 5&lt;/a&gt;, we leveraged our composable filter system by introducing the &lt;code&gt;ITenanted&lt;/code&gt; interface. This filter reads the &lt;code&gt;TenantId&lt;/code&gt; from our request-scoped &lt;code&gt;DbCtxAttributes&lt;/code&gt; and applies the filter.&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;Projected Data Support:&lt;/strong&gt;&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;Goal:&lt;/strong&gt; Resolve contextual IDs (like &lt;code&gt;TenantId&lt;/code&gt; or permission IDs) even when they are on a related entity.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Achieved:&lt;/strong&gt; Yes. This was a critical success and a key differentiator for Linq2Db.&lt;ol&gt;&lt;li&gt;&lt;strong&gt;Projected Tenancy:&lt;/strong&gt; In &lt;a href=&quot;https://byteaether.github.io/2025/building-an-enterprise-data-access-layer-composable-multi-tenancy-filtering/&quot;&gt;Part 5&lt;/a&gt;, our &lt;code&gt;Post&lt;/code&gt; entity implemented &lt;code&gt;ITenanted&lt;/code&gt; by defining its &lt;code&gt;TenantId&lt;/code&gt; via an &lt;code&gt;[ExpressionMethod]&lt;/code&gt; that navigated to its parent &lt;code&gt;User&lt;/code&gt; (&lt;code&gt;x =&amp;gt; x.User.TenantId&lt;/code&gt;).&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Projected Permissions:&lt;/strong&gt; In &lt;a href=&quot;https://byteaether.github.io/2025/building-an-enterprise-data-access-layer-composable-row-level-security/&quot;&gt;Part 6&lt;/a&gt;, our &lt;code&gt;Comment&lt;/code&gt; entity implemented &lt;code&gt;IProtected&lt;/code&gt; by projecting its permission &lt;code&gt;ObjectId&lt;/code&gt; from its parent &lt;code&gt;Post&lt;/code&gt; (&lt;code&gt;x =&amp;gt; x.Post.Id&lt;/code&gt;).&lt;/li&gt;&lt;/ol&gt;&lt;/li&gt;&lt;li&gt;In both cases, Linq2Db correctly translated these C# expressions into the necessary SQL &lt;code&gt;INNER JOIN&lt;/code&gt;s and &lt;code&gt;WHERE&lt;/code&gt; clauses.&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;Entity-Based Permissions (RLS):&lt;/strong&gt;&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;Goal:&lt;/strong&gt; Automatically filter rows that the current user is not authorized to see.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Achieved:&lt;/strong&gt; Yes. In &lt;a href=&quot;https://byteaether.github.io/2025/building-an-enterprise-data-access-layer-composable-row-level-security/&quot;&gt;Part 6&lt;/a&gt;, we built our final and most complex filter, &lt;code&gt;IProtected&lt;/code&gt;. This filter joins against the &lt;code&gt;permission&lt;/code&gt; table using the current &lt;code&gt;UserId&lt;/code&gt;, effectively implementing row-level security transparently.&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;&lt;strong&gt;Verdict:&lt;/strong&gt; We successfully implemented 100% of our initial goals. The final composed query, which Linq2Db generates by combining all these filters, is a testament to the power of this composable, interface-driven architecture.&lt;/p&gt;&lt;h2 id=&quot;future-ideas&quot; tabindex=&quot;-1&quot;&gt;Future Ideas&lt;/h2&gt;&lt;p&gt;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:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;Database Migrations and CI Pipelines:&lt;/strong&gt; We&#39;ve perfected the &lt;em&gt;runtime&lt;/em&gt; behavior of our DAL, but we&#39;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.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Full History Support:&lt;/strong&gt; 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., &amp;quot;what did this record look like on June 1st?&amp;quot;), this isn&#39;t enough. A fascinating challenge would be to implement a full temporal history or versioning system directly within the DAL.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Replicating this with EF Core:&lt;/strong&gt; Replicating this architecture in EF Core, .NET&#39;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&#39;s &amp;quot;code-first&amp;quot; preference and reliance on a Change Tracker (Unit of Work) would fundamentally alter our scaffolding and &lt;code&gt;CrudExtensions&lt;/code&gt; implementation. The largest obstacle would be projected properties, as EF Core lacks native support for &lt;code&gt;[ExpressionMethod]&lt;/code&gt;, requiring a deep investigation into community libraries to handle our projected tenancy and RLS.&lt;/li&gt;&lt;/ul&gt;&lt;h2 id=&quot;conclusion&quot; tabindex=&quot;-1&quot;&gt;Conclusion&lt;/h2&gt;&lt;p&gt;Thank you for following this series. We began with an ambitious set of goals for a &amp;quot;perfect&amp;quot; DAL, and through seven articles, we&#39;ve built it piece by piece. We have a Data Access Layer that is truly &amp;quot;enterprise-ready&amp;quot; - it is secure by default, resilient to common errors, and abstracts immense complexity away from the business logic.&lt;/p&gt;&lt;p&gt;The final code for this article, and the entire series, is available on GitHub.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Full Source Code: &lt;a href=&quot;https://github.com/ByteAether/EnterpriseDal/tree/part7&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;https://github.com/ByteAether/EnterpriseDal/tree/part7&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;I hope this series has provided a valuable blueprint for your own advanced data access architectures.&lt;/p&gt;</content></entry><entry><title>Building an Enterprise Data Access Layer: Composable Row-Level Security</title><link href="https://byteaether.github.io/2025/building-an-enterprise-data-access-layer-composable-row-level-security/"/><updated>2025-11-24T00:00:00Z</updated><id>https://byteaether.github.io/2025/building-an-enterprise-data-access-layer-composable-row-level-security/</id><content type="html">&lt;p&gt;Welcome to the sixth post in &lt;a href=&quot;https://byteaether.github.io/series/enterprise-dal/&quot;&gt;our series on building a feature-rich, automated enterprise Data Access Layer (DAL)&lt;/a&gt; using C# and &lt;a href=&quot;https://github.com/linq2db/linq2db&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Linq2Db&lt;/a&gt;. In our previous articles, we established a powerful architectural pattern: &lt;a href=&quot;https://byteaether.github.io/2025/building-an-enterprise-data-access-layer-automated-soft-delete/&quot;&gt;a composable global query filter system&lt;/a&gt;. This system allows us to define cross-cutting concerns as simple interfaces, automatically combining and applying them to every relevant query.&lt;/p&gt;&lt;p&gt;So far, we have successfully implemented:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://byteaether.github.io/2025/building-an-enterprise-data-access-layer-automated-auditing/&quot;&gt;Automated Auditing&lt;/a&gt;&lt;/strong&gt; (&lt;code&gt;ICreatable&lt;/code&gt;, &lt;code&gt;IModifiable&lt;/code&gt;) to set &lt;code&gt;CreatedAt&lt;/code&gt; and &lt;code&gt;ModifiedAt&lt;/code&gt; timestamps.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://byteaether.github.io/2025/building-an-enterprise-data-access-layer-automated-soft-delete/&quot;&gt;Transparent Soft-Deletes&lt;/a&gt;&lt;/strong&gt; (&lt;code&gt;IRemovable&lt;/code&gt;) to filter out records where &lt;code&gt;RemovedAt&lt;/code&gt; is not &lt;code&gt;NULL&lt;/code&gt;.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://byteaether.github.io/2025/building-an-enterprise-data-access-layer-composable-multi-tenancy-filtering/&quot;&gt;Composable Multi-Tenancy&lt;/a&gt;&lt;/strong&gt; (&lt;code&gt;ITenanted&lt;/code&gt;) to automatically isolate data based on a &lt;code&gt;TenantId&lt;/code&gt;, even through projected relationships.&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;In this post, we will tackle the final and most complex pillar of our automated DAL: &lt;strong&gt;entity-based row-level security (RLS)&lt;/strong&gt;. Our goal is to automatically filter any query to return only the records that the &lt;em&gt;current user&lt;/em&gt; is authorized to access. We will achieve this by extending the exact same composable filter architecture we&#39;ve already built, demonstrating its power and flexibility.&lt;/p&gt;&lt;h2 id=&quot;1.-defining-the-security-contracts&quot; tabindex=&quot;-1&quot;&gt;1. Defining the Security Contracts&lt;/h2&gt;&lt;p&gt;To build an RLS system, we need to define three core components:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;A contract for the &lt;strong&gt;current user&lt;/strong&gt;.&lt;/li&gt;&lt;li&gt;A contract for a &lt;strong&gt;permission record&lt;/strong&gt;.&lt;/li&gt;&lt;li&gt;A contract for an entity that &lt;strong&gt;requires protection&lt;/strong&gt;.&lt;/li&gt;&lt;/ol&gt;&lt;h3 id=&quot;updating-the-database-context&quot; tabindex=&quot;-1&quot;&gt;Updating the Database Context&lt;/h3&gt;&lt;p&gt;First, we must make our DAL aware of the &amp;quot;current user.&amp;quot; We &lt;a href=&quot;https://byteaether.github.io/2025/building-an-enterprise-data-access-layer-composable-multi-tenancy-filtering/&quot;&gt;previously&lt;/a&gt; extended our &lt;code&gt;IDbCtx&lt;/code&gt; with &lt;code&gt;DbCtxAttributes&lt;/code&gt; to hold the current &lt;code&gt;TenantId&lt;/code&gt;. We will now update that same record to also hold a &lt;code&gt;UserId&lt;/code&gt;.&lt;/p&gt;&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code class=&quot;language-csharp&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// In DAL.Base/IDbCtx.cs&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;interface&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;IDbCtx&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token type-list&quot;&gt;&lt;span class=&quot;token class-name&quot;&gt;IDataContext&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token return-type class-name&quot;&gt;DbCtxAttributes&lt;/span&gt; Attributes &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token return-type class-name&quot;&gt;ITable&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;IPermissionEntity&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;GetPermissions&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// New!&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;record&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;DbCtxAttributes&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Ulid&lt;/span&gt; TenantId &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;default&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Ulid&lt;span class=&quot;token punctuation&quot;&gt;?&lt;/span&gt;&lt;/span&gt; UserId &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// New!&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;We&#39;ve added two things:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;code&gt;Ulid? UserId&lt;/code&gt;: This will be set by the application (e.g., from an HTTP context) at the beginning of a request. It&#39;s nullable, which is a critical design choice. If &lt;code&gt;UserId&lt;/code&gt; is &lt;code&gt;null&lt;/code&gt;, our RLS system will treat the user as unauthenticated and (by default) deny all access to protected entities.&lt;/li&gt;&lt;li&gt;&lt;code&gt;ITable&amp;lt;IPermissionEntity&amp;gt; GetPermissions()&lt;/code&gt;: This new method is an abstraction. Our filter logic needs to query &amp;quot;permissions,&amp;quot; but it shouldn&#39;t be hard-coded to a specific &lt;code&gt;Permission&lt;/code&gt; table. This interface-based approach allows our &lt;code&gt;DbCtx&lt;/code&gt; implementation to provide the correct table, promoting loose coupling.&lt;/li&gt;&lt;/ul&gt;&lt;h3 id=&quot;the-permission-contract-(ipermissionentity)&quot; tabindex=&quot;-1&quot;&gt;The Permission Contract (&lt;code&gt;IPermissionEntity&lt;/code&gt;)&lt;/h3&gt;&lt;p&gt;Next, we need a way to represent a permission. In our database, we&#39;ve added a new &lt;code&gt;permission&lt;/code&gt; table with a simple schema: &lt;code&gt;subject_id&lt;/code&gt; (who has the permission) and &lt;code&gt;object_id&lt;/code&gt; (what entity the permission is for).&lt;/p&gt;&lt;p&gt;To map this in code, we create a new interface:&lt;/p&gt;&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code class=&quot;language-csharp&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// In DAL.Base/EntityBehavior/IPermissionEntity.cs&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;interface&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;IPermissionEntity&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token return-type class-name&quot;&gt;Ulid&lt;/span&gt; SubjectId &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// The &quot;Who&quot; (e.g., a UserId)&lt;/span&gt; &lt;span class=&quot;token return-type class-name&quot;&gt;Ulid&lt;/span&gt; ObjectId &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// The &quot;What&quot; (e.g., a PostId)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;After running our scaffolder to generate the &lt;code&gt;Permission&lt;/code&gt; entity from the new table, we simply create a partial class to apply this interface:&lt;/p&gt;&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code class=&quot;language-csharp&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// In DAL.Context/Entity/Permission.cs&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;partial&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Permission&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token type-list&quot;&gt;&lt;span class=&quot;token class-name&quot;&gt;IPermissionEntity&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Finally, we implement the new &lt;code&gt;GetPermissions()&lt;/code&gt; method in our &lt;code&gt;DbCtx.cs&lt;/code&gt; file. It&#39;s a simple one-line pass-through:&lt;/p&gt;&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code class=&quot;language-csharp&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// In DAL.Context/DbCtx.cs&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token return-type class-name&quot;&gt;ITable&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;IPermissionEntity&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;GetPermissions&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token generic-method&quot;&gt;&lt;span class=&quot;token function&quot;&gt;GetTable&lt;/span&gt;&lt;span class=&quot;token generic class-name&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;Permission&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&quot;2.-the-iprotected-interface%3A-the-rls-filter&quot; tabindex=&quot;-1&quot;&gt;2. The &lt;code&gt;IProtected&lt;/code&gt; Interface: The RLS Filter&lt;/h2&gt;&lt;p&gt;With our contracts in place, we can now create the &amp;quot;behavior&amp;quot; interface that will automatically apply our RLS filter. We&#39;ll call it &lt;code&gt;IProtected&lt;/code&gt;. This interface is the heart of our new system and leverages the same &lt;code&gt;[EntityFilter]&lt;/code&gt; attribute we used for soft-deletes and tenancy.&lt;/p&gt;&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code class=&quot;language-csharp&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// In DAL.Base/EntityBehavior/IProtected.cs&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token generic-method&quot;&gt;&lt;span class=&quot;token function&quot;&gt;EntityFilter&lt;/span&gt;&lt;span class=&quot;token generic class-name&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;IProtected&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;nameof&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;Filter&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;interface&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;IProtected&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token return-type class-name&quot;&gt;Ulid&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;GetPermissionObjectId&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;token return-type class-name&quot;&gt;IQueryable&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;T&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token generic-method&quot;&gt;&lt;span class=&quot;token function&quot;&gt;Filter&lt;/span&gt;&lt;span class=&quot;token generic class-name&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;T&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;IQueryable&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;T&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; q&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;IDbCtx&lt;/span&gt; dbCtx&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;where&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;T&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token type-list&quot;&gt;&lt;span class=&quot;token class-name&quot;&gt;IProtected&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; dbCtx&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Attributes&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;UserId &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// 1. If no user is in context, return nothing.&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;?&lt;/span&gt; q&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Where&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;_ &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// 2. Otherwise, join with permissions and filter.&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; q&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;InnerJoin&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt; dbCtx&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;GetPermissions&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;entity&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; perm&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; perm&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ObjectId &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; entity&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;GetPermissionObjectId&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; perm&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;SubjectId &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; dbCtx&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Attributes&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;UserId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;entity&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; perm&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; entity &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Let&#39;s break this down, as it&#39;s the most important piece:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;&lt;code&gt;[EntityFilter&amp;lt;...&amp;gt;]&lt;/code&gt;: This attribute, as before, tells our DAL&#39;s bootstrapper to find the &lt;code&gt;Filter&lt;/code&gt; method and apply it to any entity that implements &lt;code&gt;IProtected&lt;/code&gt;.&lt;/li&gt;&lt;li&gt;&lt;code&gt;Ulid GetPermissionObjectId()&lt;/code&gt;: This is the contract. Any entity implementing &lt;code&gt;IProtected&lt;/code&gt; &lt;em&gt;must&lt;/em&gt; tell the system which &lt;code&gt;ObjectId&lt;/code&gt; to use for its permission check. This is a method, not a property, which allows for incredible flexibility, as we&#39;ll see next.&lt;/li&gt;&lt;li&gt;&lt;code&gt;private static IQueryable&amp;lt;T&amp;gt; Filter&amp;lt;...&amp;gt;&lt;/code&gt;: This is the filter logic.&lt;ul&gt;&lt;li&gt;&lt;strong&gt;The Security Check:&lt;/strong&gt; First, it checks &lt;code&gt;dbCtx.Attributes.UserId == null&lt;/code&gt;. If true, it returns &lt;code&gt;q.Where(_ =&amp;gt; false)&lt;/code&gt;. This is a &amp;quot;fail-closed&amp;quot; security principle. If we don&#39;t know who the user is, they get to see &lt;em&gt;nothing&lt;/em&gt;.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;The RLS Join:&lt;/strong&gt; If a user &lt;em&gt;is&lt;/em&gt; present, it performs an &lt;code&gt;InnerJoin&lt;/code&gt; between the entity query (&lt;code&gt;q&lt;/code&gt;) and the permissions table (&lt;code&gt;dbCtx.GetPermissions()&lt;/code&gt;).&lt;/li&gt;&lt;li&gt;&lt;strong&gt;The Join Condition:&lt;/strong&gt; This is the core logic: &lt;code&gt;perm.ObjectId == entity.GetPermissionObjectId() &amp;amp;&amp;amp; perm.SubjectId == dbCtx.Attributes.UserId&lt;/code&gt;. It translates to: &amp;quot;Find a permission record where the &lt;code&gt;ObjectId&lt;/code&gt; matches the ID the entity provides, AND the &lt;code&gt;SubjectId&lt;/code&gt; matches the current user in the context.&amp;quot;&lt;/li&gt;&lt;li&gt;&lt;strong&gt;The Result:&lt;/strong&gt; The final lambda &lt;code&gt;(entity, perm) =&amp;gt; entity&lt;/code&gt; simply returns the original entity. The &lt;code&gt;InnerJoin&lt;/code&gt; acts as a pure filter, discarding any &lt;code&gt;entity&lt;/code&gt; that doesn&#39;t have a matching permission record for the current user.&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;&lt;/ol&gt;&lt;h2 id=&quot;3.-implementation%3A-projected-permissions&quot; tabindex=&quot;-1&quot;&gt;3. Implementation: Projected Permissions&lt;/h2&gt;&lt;p&gt;Now, let&#39;s apply our new &lt;code&gt;IProtected&lt;/code&gt; interface to an entity. A common scenario is that permissions aren&#39;t granted to granular entities like &lt;code&gt;Comment&lt;/code&gt;, but rather to their parent, like &lt;code&gt;Post&lt;/code&gt;. A user who can access a &lt;code&gt;Post&lt;/code&gt; can access all of its &lt;code&gt;Comment&lt;/code&gt;s.&lt;/p&gt;&lt;p&gt;Our &lt;code&gt;IProtected&lt;/code&gt; interface&#39;s &lt;code&gt;GetPermissionObjectId()&lt;/code&gt; method is designed for precisely this. We can implement it on our &lt;code&gt;Comment&lt;/code&gt; entity to return its parent &lt;code&gt;Post.Id&lt;/code&gt;. This is &lt;a href=&quot;https://byteaether.github.io/2025/building-an-enterprise-data-access-layer-composable-multi-tenancy-filtering/&quot;&gt;the &lt;em&gt;exact&lt;/em&gt; same &amp;quot;projected&amp;quot; logic we used for multi-tenancy&lt;/a&gt;.&lt;/p&gt;&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code class=&quot;language-csharp&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// In DAL.Context/Entity/Comment.cs&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;partial&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Comment&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token type-list&quot;&gt;&lt;span class=&quot;token class-name&quot;&gt;IProtected&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// This is the implementation of IProtected.GetPermissionObjectId()&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;ExpressionMethod&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;nameof&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;GetPermissionObjectIdExpression&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token return-type class-name&quot;&gt;Ulid&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;GetPermissionObjectId&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; Post&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Id&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;token return-type class-name&quot;&gt;Expression&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;Func&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;Comment&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; Ulid&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;GetPermissionObjectIdExpression&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; x &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; x&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Post&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Id&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;By using Linq2Db&#39;s &lt;code&gt;[ExpressionMethod]&lt;/code&gt;, we are telling the query engine that when it sees &lt;code&gt;entity.GetPermissionObjectId()&lt;/code&gt; inside our &lt;code&gt;Filter&lt;/code&gt;&#39;s &lt;code&gt;InnerJoin&lt;/code&gt;, it should substitute the expression &lt;code&gt;x =&amp;gt; x.Post.Id&lt;/code&gt;.&lt;/p&gt;&lt;p&gt;The RLS filter logic will now transparently translate to: &lt;code&gt;perm.ObjectId == comment.Post.Id&lt;/code&gt;. The DAL will automatically join the &lt;code&gt;Comment&lt;/code&gt; table to the &lt;code&gt;Post&lt;/code&gt; table to get the &lt;code&gt;Id&lt;/code&gt; for the check, all without the developer ever thinking about it.&lt;/p&gt;&lt;h2 id=&quot;4.-the-payoff%3A-the-final-composed-query&quot; tabindex=&quot;-1&quot;&gt;4. The Payoff: The Final Composed Query&lt;/h2&gt;&lt;p&gt;We have now layered four distinct, automated behaviors onto our entities:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;&lt;code&gt;ICreatable&lt;/code&gt; &amp;amp; &lt;code&gt;IModifiable&lt;/code&gt; (Auditing)&lt;/li&gt;&lt;li&gt;&lt;code&gt;IRemovable&lt;/code&gt; (Soft-Delete)&lt;/li&gt;&lt;li&gt;&lt;code&gt;ITenanted&lt;/code&gt; (Multi-Tenancy)&lt;/li&gt;&lt;li&gt;&lt;code&gt;IProtected&lt;/code&gt; (Row-Level Security)&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;Let&#39;s see what happens when we execute a simple business operation, like modifying a comment. We add this code to our &lt;code&gt;Program.cs&lt;/code&gt;:&lt;/p&gt;&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code class=&quot;language-csharp&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// Set DB Context parameters&lt;/span&gt; ctx&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Attributes &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt; tenant&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Id&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; u&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Id &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// ...&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// Try to update a protected comment entity&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; ctx&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token generic-method&quot;&gt;&lt;span class=&quot;token function&quot;&gt;GetTable&lt;/span&gt;&lt;span class=&quot;token generic class-name&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;Comment&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Set&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;x &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; x&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Content&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;asd123&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;ModifyAsync&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Remember, our custom &lt;code&gt;ModifyAsync&lt;/code&gt; extension also automatically handles setting the &lt;code&gt;ModifiedAt&lt;/code&gt; timestamp. The developer wrote a simple, two-line update. Our DAL, however, composes &lt;em&gt;all&lt;/em&gt; the rules and generates the following SQL:&lt;/p&gt;&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;UPDATE&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;comment&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;SET&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;content&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;asd123&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;-- 1. &quot;ModifyAsync&quot; Auditing&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;modified_at&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;2025-11-04 12:34:11.899&#39;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;post&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;a_Post&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;a_User&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;-- 4. RLS Join&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;permission&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;b&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;WHERE&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;-- 2. Soft-Delete Filters (on all joined tables)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;a_User&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;removed_at&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;IS&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;NULL&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;AND&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;a_Post&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;removed_at&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;IS&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;NULL&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;AND&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;comment&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;removed_at&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;IS&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;NULL&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;AND&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;-- 3. Projected Multi-Tenancy Filter&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;a_User&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;tenant_id&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; X&lt;span class=&quot;token string&quot;&gt;&#39;019A4EDC4AD87A53C22679FA88F15885&#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;AND&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;-- Joins for projections&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;comment&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;post_id&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;a_Post&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;id&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;AND&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;a_Post&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;user_id&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;a_User&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;id&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;AND&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;-- 4. Projected Row-Level Security Filter&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;b&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;object_id&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;a_Post&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;id&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;AND&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;b&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;subject_id&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; X&lt;span class=&quot;token string&quot;&gt;&#39;019A4EDC4ADE42CA89BA742103A7A723&#39;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This query is a perfect demonstration of our architecture&#39;s power:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;&lt;strong&gt;Auditing:&lt;/strong&gt; &lt;code&gt;ModifyAsync&lt;/code&gt; correctly injected the &lt;code&gt;modified_at&lt;/code&gt; SET clause.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Soft-Delete:&lt;/strong&gt; The &lt;code&gt;[removed_at] IS NULL&lt;/code&gt; filter was applied to &lt;code&gt;comment&lt;/code&gt;, &lt;code&gt;post&lt;/code&gt;, AND &lt;code&gt;user&lt;/code&gt;.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Multi-Tenancy:&lt;/strong&gt; The projected tenancy filter (&lt;code&gt;[a_User].[tenant_id] = ...&lt;/code&gt;) is present, ensuring we don&#39;t even &lt;em&gt;see&lt;/em&gt; comments from other tenants.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Row-Level Security:&lt;/strong&gt; The new &lt;code&gt;IProtected&lt;/code&gt; filter joined the &lt;code&gt;permission&lt;/code&gt; table (&lt;code&gt;[b]&lt;/code&gt;) and correctly filtered on the &lt;em&gt;Post ID&lt;/em&gt; (&lt;code&gt;[b].[object_id] = [a_Post].[id]&lt;/code&gt;) and the current &lt;em&gt;User ID&lt;/em&gt; (&lt;code&gt;[b].[subject_id] = ...&lt;/code&gt;).&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;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 &lt;em&gt;all&lt;/em&gt; correctly and automatically enforced.&lt;/p&gt;&lt;h2 id=&quot;conclusion&quot; tabindex=&quot;-1&quot;&gt;Conclusion&lt;/h2&gt;&lt;p&gt;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.&lt;/p&gt;&lt;p&gt;By separating the &lt;em&gt;definition&lt;/em&gt; of a behavior (the interface) from its &lt;em&gt;implementation&lt;/em&gt; (the filter logic), we&#39;ve created a system that is robust, testable, and incredibly easy to extend.&lt;/p&gt;&lt;p&gt;As always, the complete, working implementation for this post is available in our GitHub repository: &lt;strong&gt;&lt;a href=&quot;https://github.com/ByteAether/EnterpriseDal/tree/part6&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;https://github.com/ByteAether/EnterpriseDal/tree/part6&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;In our next post, we will address the final piece of our auditing system: automatically populating the &lt;code&gt;CreatedBy&lt;/code&gt; and &lt;code&gt;ModifiedBy&lt;/code&gt; fields, leveraging the new &lt;code&gt;UserId&lt;/code&gt; we just added to our &lt;code&gt;DbCtxAttributes&lt;/code&gt;.&lt;/p&gt;</content></entry><entry><title>Announcing ByteAether.Ulid 1.3.2: .NET 10 Support and Optimized Design</title><link href="https://byteaether.github.io/2025/announcing-byteaetherulid-132-net-10-support-and-optimized-design/"/><updated>2025-11-14T00:00:00Z</updated><id>https://byteaether.github.io/2025/announcing-byteaetherulid-132-net-10-support-and-optimized-design/</id><content type="html">&lt;p&gt;We are excited to announce the release of &lt;strong&gt;ByteAether.Ulid version 1.3.2&lt;/strong&gt;, which is now available on &lt;a href=&quot;https://www.nuget.org/packages/ByteAether.Ulid/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;NuGet&lt;/a&gt;. This release introduces official support and dedicated binaries for the new &lt;strong&gt;.NET 10&lt;/strong&gt; platform, continuing our mission to provide the most performant, secure, and specification-compliant &lt;a href=&quot;https://github.com/ulid/spec&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;ULID (Universally Unique Lexicographically Sortable Identifier)&lt;/a&gt; implementation for the .NET ecosystem.&lt;/p&gt;&lt;p&gt;For those new to the project, &lt;a href=&quot;https://github.com/ByteAether/Ulid&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;ByteAether.Ulid&lt;/a&gt; provides a robust solution engineered to solve critical, often-overlooked issues in unique identifier generation, such as reliable overflow handling and secure monotonic generation.&lt;/p&gt;&lt;h2 id=&quot;embracing-.net-10-and-finalizing-c%23-14-features&quot; tabindex=&quot;-1&quot;&gt;Embracing .NET 10 and Finalizing C# 14 Features&lt;/h2&gt;&lt;p&gt;The primary feature of version 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;This release also solidifies our use of modern C# features. We were an early adopter of C# 14&#39;s &amp;quot;preview&amp;quot; features, specifically the &lt;code&gt;field&lt;/code&gt; keyword. With the official release of .NET 10, we can now move from &lt;code&gt;LangVersion&lt;/code&gt; &amp;quot;preview&amp;quot; to &amp;quot;latest,&amp;quot; locking in the benefits this feature provides.&lt;/p&gt;&lt;p&gt;We use this feature in our &lt;code&gt;GenerationOptions&lt;/code&gt; record to perform &amp;quot;fail-fast&amp;quot; validation. By validating a property &lt;em&gt;once&lt;/em&gt; during its &lt;code&gt;init&lt;/code&gt; assignment, we avoid all validation overhead on subsequent &lt;code&gt;get&lt;/code&gt; operations or, more importantly, inside the &lt;code&gt;Ulid.New()&lt;/code&gt; hot path.&lt;/p&gt;&lt;p&gt;For example, here is how we ensure that the &lt;code&gt;Monotonicity&lt;/code&gt; property is always set to a valid &lt;code&gt;enum&lt;/code&gt; value:&lt;/p&gt;&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code class=&quot;language-csharp&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token return-type class-name&quot;&gt;MonotonicityOptions&lt;/span&gt; Monotonicity &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; init &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; field &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Enum&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;IsDefined&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;typeof&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token type-expression class-name&quot;&gt;MonotonicityOptions&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;value&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;throw&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token constructor-invocation class-name&quot;&gt;ArgumentOutOfRangeException&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;nameof&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;Monotonicity&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Invalid monotonicity option.&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; MonotonicityOptions&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;MonotonicIncrement&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This pattern provides a critical optimization. The validation logic runs a single time when the &lt;code&gt;GenerationOptions&lt;/code&gt; object is constructed, ensuring that any call to &lt;code&gt;Ulid.New()&lt;/code&gt; using these options operates with pre-validated, zero-overhead configuration.&lt;/p&gt;&lt;h2 id=&quot;a-deeper-look%3A-our-multi-targeting-strategy&quot; tabindex=&quot;-1&quot;&gt;A Deeper Look: Our Multi-Targeting Strategy&lt;/h2&gt;&lt;p&gt;Our NuGet package ships distinct, optimized artifacts for a wide range of targets:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;.NET 10&lt;/strong&gt;&lt;/li&gt;&lt;li&gt;&lt;strong&gt;.NET 9&lt;/strong&gt;&lt;/li&gt;&lt;li&gt;&lt;strong&gt;.NET 8&lt;/strong&gt;&lt;/li&gt;&lt;li&gt;&lt;strong&gt;.NET 7&lt;/strong&gt;&lt;/li&gt;&lt;li&gt;&lt;strong&gt;.NET 6&lt;/strong&gt;&lt;/li&gt;&lt;li&gt;&lt;strong&gt;.NET 5&lt;/strong&gt;&lt;/li&gt;&lt;li&gt;&lt;strong&gt;.NET Standard 2.1&lt;/strong&gt;&lt;/li&gt;&lt;li&gt;&lt;strong&gt;.NET Standard 2.0&lt;/strong&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;This multi-targeting strategy is fundamental to our performance promise. We avoid the &amp;quot;lowest common denominator&amp;quot; approach of only shipping a &lt;code&gt;.NET Standard 2.0&lt;/code&gt; assembly.&lt;/p&gt;&lt;p&gt;When you install &lt;code&gt;ByteAether.Ulid&lt;/code&gt; in a .NET 10 project, NuGet automatically selects the &lt;code&gt;.NET 10&lt;/code&gt; 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.&lt;/p&gt;&lt;p&gt;For example, our parsing (&lt;code&gt;Ulid.Parse()&lt;/code&gt;) and stringification (&lt;code&gt;ulid.ToString()&lt;/code&gt;) routines use optimized &lt;code&gt;ReadOnlySpan&amp;lt;T&amp;gt;&lt;/code&gt; and &lt;code&gt;MemoryMarshal&lt;/code&gt; APIs on modern targets, often leveraging SIMD for Base32 operations. This is a primary reason for our benchmark-leading performance, and it&#39;s an advantage you would lose if you only consumed a &lt;code&gt;.NET Standard 2.0&lt;/code&gt;-compiled library.&lt;/p&gt;&lt;h2 id=&quot;how-this-philosophy-shapes-our-core-features&quot; tabindex=&quot;-1&quot;&gt;How This Philosophy Shapes Our Core Features&lt;/h2&gt;&lt;p&gt;This focus on performance and robust design is directly applied to the library&#39;s core features, particularly in how we handle security and reliability.&lt;/p&gt;&lt;h3 id=&quot;reliable-overflow-handling&quot; tabindex=&quot;-1&quot;&gt;Reliable Overflow Handling&lt;/h3&gt;&lt;p&gt;A &lt;a href=&quot;https://github.com/ulid/spec/issues/39&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;critical, and often ignored, problem&lt;/a&gt; in ULID generation is the &lt;code&gt;OverflowException&lt;/code&gt; 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 &lt;em&gt;random&lt;/em&gt;. This means a newly generated ULID could, by chance, have a random component already near its maximum value.&lt;/p&gt;&lt;p&gt;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.&lt;/p&gt;&lt;p&gt;We&#39;ve written a detailed analysis of this problem and our solution here: &lt;strong&gt;&lt;a href=&quot;https://byteaether.github.io/2025/prioritizing-reliability-when-milliseconds-arent-enough/&quot;&gt;Prioritizing Reliability: When Milliseconds Aren&#39;t Enough&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;&lt;h3 id=&quot;secure-and-configurable-generation&quot; tabindex=&quot;-1&quot;&gt;Secure and Configurable Generation&lt;/h3&gt;&lt;p&gt;The &lt;code&gt;GenerationOptions&lt;/code&gt; class, which benefits from the &lt;code&gt;field&lt;/code&gt; keyword mentioned earlier, is the key to our advanced security and performance tuning.&lt;/p&gt;&lt;p&gt;It allows you to configure &lt;code&gt;MonotonicityOptions&lt;/code&gt; (e.g., &lt;code&gt;MonotonicRandom1Byte&lt;/code&gt;) to &lt;a href=&quot;https://github.com/ulid/spec/issues/105&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;defeat enumeration attacks, a vulnerability in simple &lt;code&gt;+1&lt;/code&gt; incrementors&lt;/a&gt;. It also lets you control the &lt;code&gt;IRandomProvider&lt;/code&gt; 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.&lt;/p&gt;&lt;p&gt;You can read a deep dive into these options and their security implications here: &lt;strong&gt;&lt;a href=&quot;https://byteaether.github.io/2025/byteaetherulid-v130-enhanced-ulid-generation-control-and-security/&quot;&gt;ByteAether.Ulid v1.3.0: Enhanced ULID Generation Control and Security&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;&lt;h2 id=&quot;conclusion&quot; tabindex=&quot;-1&quot;&gt;Conclusion&lt;/h2&gt;&lt;p&gt;The 1.3.2 release of &lt;strong&gt;ByteAether.Ulid&lt;/strong&gt;, with its .NET 10 support, continues our commitment to providing a ULID implementation that is fast, secure, and meticulously spec-compliant.&lt;/p&gt;&lt;p&gt;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.&lt;/p&gt;&lt;p&gt;We encourage all architects and senior engineers who value performance, security, and specification-compliance to adopt ByteAether.Ulid for their identifier generation needs.&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;Get the package on NuGet:&lt;/strong&gt; &lt;a href=&quot;https://www.nuget.org/packages/ByteAether.Ulid/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;https://www.nuget.org/packages/ByteAether.Ulid/&lt;/a&gt;&lt;pre class=&quot;language-sh&quot;&gt;&lt;code class=&quot;language-sh&quot;&gt;dotnet &lt;span class=&quot;token function&quot;&gt;add&lt;/span&gt; package ByteAether.Ulid&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Explore the code, read the full README, and contribute on GitHub:&lt;/strong&gt; &lt;a href=&quot;https://github.com/ByteAether/Ulid&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;https://github.com/ByteAether/Ulid&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;</content></entry><entry><title>Building an Enterprise Data Access Layer: Composable Multi-Tenancy Filtering</title><link href="https://byteaether.github.io/2025/building-an-enterprise-data-access-layer-composable-multi-tenancy-filtering/"/><updated>2025-11-11T00:00:00Z</updated><id>https://byteaether.github.io/2025/building-an-enterprise-data-access-layer-composable-multi-tenancy-filtering/</id><content type="html">&lt;p&gt;In our &lt;a href=&quot;https://byteaether.github.io/series/enterprise-dal/&quot;&gt;previous articles&lt;/a&gt;, we established a robust foundation for our enterprise Data Access Layer (DAL). We &lt;a href=&quot;https://byteaether.github.io/2025/building-an-enterprise-data-access-layer-database-and-code-structure/&quot;&gt;began with a database-first approach&lt;/a&gt; using C# and &lt;a href=&quot;https://github.com/linq2db/linq2db&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Linq2Db&lt;/a&gt;, then &lt;a href=&quot;https://byteaether.github.io/2025/building-an-enterprise-data-access-layer-automated-auditing/&quot;&gt;implemented automated auditing&lt;/a&gt; for &lt;code&gt;CreatedAt&lt;/code&gt; and &lt;code&gt;ModifiedAt&lt;/code&gt; timestamps. Most recently, &lt;a href=&quot;https://byteaether.github.io/2025/building-an-enterprise-data-access-layer-automated-soft-delete/&quot;&gt;we engineered a powerful, composable global query filter system&lt;/a&gt; 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.&lt;/p&gt;&lt;p&gt;Multi-tenancy ensures that one tenant&#39;s data is strictly isolated from another&#39;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.&lt;/p&gt;&lt;p&gt;We will extend our entity behavior model by introducing an &lt;code&gt;ITenanted&lt;/code&gt; 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 &amp;quot;projected tenancy,&amp;quot; where an entity&#39;s tenant affiliation is derived from a related entity.&lt;/p&gt;&lt;p&gt;The complete source code for this part is available on GitHub: &lt;a href=&quot;https://github.com/ByteAether/EnterpriseDal/tree/part5&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;https://github.com/ByteAether/EnterpriseDal/tree/part5&lt;/a&gt;.&lt;/p&gt;&lt;h2 id=&quot;1.-establishing-tenant-context-in-the-dal&quot; tabindex=&quot;-1&quot;&gt;1. Establishing Tenant Context in the DAL&lt;/h2&gt;&lt;p&gt;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.&lt;/p&gt;&lt;p&gt;Previously, our &lt;code&gt;IDbCtx&lt;/code&gt; was a simple marker interface. We will now extend it to carry request-scoped attributes, starting with the &lt;code&gt;TenantId&lt;/code&gt;.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;File: &lt;code&gt;DAL.Base/IDbCtx.cs&lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code class=&quot;language-csharp&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;namespace&lt;/span&gt; &lt;span class=&quot;token namespace&quot;&gt;DAL&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Base&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;interface&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;IDbCtx&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token type-list&quot;&gt;&lt;span class=&quot;token class-name&quot;&gt;IDataContext&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token return-type class-name&quot;&gt;DbCtxAttributes&lt;/span&gt; Attributes &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;record&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;DbCtxAttributes&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Ulid&lt;/span&gt; TenantId &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;default&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;By adding the &lt;code&gt;Attributes&lt;/code&gt; property, we provide a dedicated, extensible home for contextual data. We use a C# &lt;code&gt;record&lt;/code&gt; for &lt;code&gt;DbCtxAttributes&lt;/code&gt; to create a simple, immutable data carrier. The &lt;code&gt;IDbCtx&lt;/code&gt; implementation (our &lt;code&gt;DbCtx&lt;/code&gt; 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 &lt;code&gt;TenantId&lt;/code&gt; available for the lifetime of that request.&lt;/p&gt;&lt;p&gt;For our demonstration, we will manually set this value in &lt;code&gt;Program.cs&lt;/code&gt; before executing any queries. This simulates how a tenant&#39;s context would be established at the start of an operation.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;File: &lt;code&gt;App/Program.cs&lt;/code&gt; (Addition)&lt;/strong&gt;&lt;/p&gt;&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code class=&quot;language-csharp&quot;&gt;&lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt;&lt;/span&gt; tenantId &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Ulid&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;New&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; ctx&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Attributes &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token named-parameter punctuation&quot;&gt;TenantId&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; tenantId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;While we use a new Ulid here, the key is that this specific value will later appear in the generated SQL &lt;code&gt;WHERE&lt;/code&gt; clause, confirming that our filter is using the context we&#39;ve provided.&lt;/p&gt;&lt;h2 id=&quot;2.-defining-the-tenancy-contract%3A-the-itenanted-interface&quot; tabindex=&quot;-1&quot;&gt;2. Defining the Tenancy Contract: The &lt;code&gt;ITenanted&lt;/code&gt; Interface&lt;/h2&gt;&lt;p&gt;Following the pattern established with &lt;code&gt;ICreatable&lt;/code&gt; and &lt;code&gt;IRemovable&lt;/code&gt;, 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 &lt;code&gt;TenantId&lt;/code&gt;, and it hooks into our global filter system.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;File: &lt;code&gt;DAL.Base/EntityBehavior/ITenanted.cs&lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code class=&quot;language-csharp&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;namespace&lt;/span&gt; &lt;span class=&quot;token namespace&quot;&gt;DAL&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Base&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;EntityBehavior&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token generic-method&quot;&gt;&lt;span class=&quot;token function&quot;&gt;EntityFilter&lt;/span&gt;&lt;span class=&quot;token generic class-name&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;ITenanted&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;nameof&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;Filter&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;interface&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ITenanted&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token type-list&quot;&gt;&lt;span class=&quot;token class-name&quot;&gt;IEntity&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token return-type class-name&quot;&gt;Ulid&lt;/span&gt; TenantId &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;token return-type class-name&quot;&gt;IQueryable&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;T&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token generic-method&quot;&gt;&lt;span class=&quot;token function&quot;&gt;Filter&lt;/span&gt;&lt;span class=&quot;token generic class-name&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;T&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;IQueryable&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;T&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; q&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;IDbCtx&lt;/span&gt; ctx&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;where&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;T&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token type-list&quot;&gt;&lt;span class=&quot;token class-name&quot;&gt;ITenanted&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; q&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Where&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;x &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; x&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;TenantId &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; ctx&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Attributes&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;TenantId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This implementation is concise but powerful, directly leveraging our previous work:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;&lt;strong&gt;&lt;code&gt;Ulid TenantId { get; }&lt;/code&gt;&lt;/strong&gt;: This property contract mandates that any class implementing &lt;code&gt;ITenanted&lt;/code&gt; must provide a &lt;code&gt;TenantId&lt;/code&gt; getter. This is the key piece of information needed for filtering.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;&lt;code&gt;[EntityFilter&amp;lt;ITenanted&amp;gt;(nameof(Filter))]&lt;/code&gt;&lt;/strong&gt;: This is the connection to our composable filter architecture. We are associating the &lt;code&gt;ITenanted&lt;/code&gt; interface with a specific filter-providing method named &lt;code&gt;Filter&lt;/code&gt;.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;&lt;code&gt;private static IQueryable&amp;lt;T&amp;gt; Filter(...)&lt;/code&gt;&lt;/strong&gt;: This method contains the filtering logic itself. It receives the current queryable (&lt;code&gt;q&lt;/code&gt;) and the database context (&lt;code&gt;ctx&lt;/code&gt;). It then appends a standard LINQ &lt;code&gt;Where&lt;/code&gt; clause, comparing the entity&#39;s &lt;code&gt;TenantId&lt;/code&gt; with the &lt;code&gt;TenantId&lt;/code&gt; we stored in the context&#39;s &lt;code&gt;Attributes&lt;/code&gt;.&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;When the DAL is initialized, our &lt;code&gt;MappingSchema.ApplyEntityFilters&amp;lt;DbCtx&amp;gt;()&lt;/code&gt; helper will discover this attribute and automatically apply this &lt;code&gt;Where&lt;/code&gt; clause to every query against any entity that implements &lt;code&gt;ITenanted&lt;/code&gt;.&lt;/p&gt;&lt;h2 id=&quot;3.-automating-interface-scaffolding&quot; tabindex=&quot;-1&quot;&gt;3. Automating Interface Scaffolding&lt;/h2&gt;&lt;p&gt;To maintain our database-first philosophy, we must ensure that our generated entity classes automatically implement the &lt;code&gt;ITenanted&lt;/code&gt; interface if their corresponding database tables contain a &lt;code&gt;tenant_id&lt;/code&gt; column. This task is perfectly suited for our custom scaffolding interceptor.&lt;/p&gt;&lt;p&gt;We will add a simple check to the interceptor&#39;s logic.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;File: &lt;code&gt;DAL.ScaffoldInterceptor/Interceptor.cs&lt;/code&gt; (Addition)&lt;/strong&gt;&lt;/p&gt;&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code class=&quot;language-csharp&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// ITenanted&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt;&lt;/span&gt; tenantIdField &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; entityModel&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Columns&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;FirstOrDefault&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;x &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; x&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Property&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Name &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;nameof&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;ITenanted&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;TenantId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;tenantIdField &lt;span class=&quot;token keyword&quot;&gt;is&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;not&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; addedInterfaces&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Add&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;typeof&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token type-expression class-name&quot;&gt;ITenanted&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This code snippet inspects the columns of the entity being scaffolded. If it finds a column that maps to a property named &lt;code&gt;TenantId&lt;/code&gt;, it adds &lt;code&gt;ITenanted&lt;/code&gt; to the list of interfaces for the generated partial class. With this change, entities like &lt;code&gt;user&lt;/code&gt; which have a direct &lt;code&gt;tenant_id&lt;/code&gt; column will be automatically protected by our tenancy filter without any manual intervention.&lt;/p&gt;&lt;h2 id=&quot;4.-the-advanced-case%3A-projected-tenancy&quot; tabindex=&quot;-1&quot;&gt;4. The Advanced Case: Projected Tenancy&lt;/h2&gt;&lt;p&gt;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 &lt;code&gt;post&lt;/code&gt; table. Its schema does not contain a &lt;code&gt;tenant_id&lt;/code&gt; column. However, a post is unequivocally owned by a tenant because it belongs to a &lt;code&gt;user&lt;/code&gt;, and that &lt;code&gt;user&lt;/code&gt; belongs to a &lt;code&gt;tenant&lt;/code&gt;. The &lt;code&gt;Post&lt;/code&gt; entity&#39;s tenancy is therefore &lt;em&gt;projected&lt;/em&gt; from its parent &lt;code&gt;User&lt;/code&gt; entity.&lt;/p&gt;&lt;p&gt;Our DAL must enforce this projected relationship as a filtering rule. We need to make the &lt;code&gt;Post&lt;/code&gt; entity implement &lt;code&gt;ITenanted&lt;/code&gt;, but how do we implement the &lt;code&gt;TenantId&lt;/code&gt; property when there is no backing column?&lt;/p&gt;&lt;p&gt;The solution lies in creating an expression-based property that Linq2Db can translate directly into SQL. We achieve this by manually creating a partial class for &lt;code&gt;Post&lt;/code&gt; and using the &lt;a href=&quot;http://blog.linq2db.com/2016/06/how-to-teach-linq-to-db-convert-custom.html&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;&lt;code&gt;[ExpressionMethod]&lt;/code&gt;&lt;/a&gt; attribute.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;File: &lt;code&gt;DAL.Context/Entity/Post.cs&lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code class=&quot;language-csharp&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;namespace&lt;/span&gt; &lt;span class=&quot;token namespace&quot;&gt;DAL&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Context&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Entity&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;partial&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Post&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token type-list&quot;&gt;&lt;span class=&quot;token class-name&quot;&gt;ITenanted&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token attribute&quot;&gt;&lt;span class=&quot;token class-name&quot;&gt;ExpressionMethod&lt;/span&gt;&lt;span class=&quot;token attribute-arguments&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;nameof&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;GetTenantIdExpression&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token return-type class-name&quot;&gt;Ulid&lt;/span&gt; TenantId &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;GetTenantIdExpression&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Compile&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;token return-type class-name&quot;&gt;Expression&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;Func&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;Post&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; Ulid&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;GetTenantIdExpression&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; x &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; x&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;User&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;TenantId&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Let&#39;s dissect this implementation, as it is central to the power of our DAL:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;&lt;strong&gt;&lt;code&gt;public partial class Post : ITenanted&lt;/code&gt;&lt;/strong&gt;: We explicitly declare that &lt;code&gt;Post&lt;/code&gt; conforms to the &lt;code&gt;ITenanted&lt;/code&gt; contract. We do this manually because our scaffolder did not find a &lt;code&gt;TenantId&lt;/code&gt; column.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;&lt;code&gt;private static Expression&amp;lt;Func&amp;lt;Post, Ulid&amp;gt;&amp;gt; GetTenantIdExpression()&lt;/code&gt;&lt;/strong&gt;: Instead of a simple value, we define the logic for retrieving the &lt;code&gt;TenantId&lt;/code&gt; as an &lt;em&gt;expression tree&lt;/em&gt;. The expression &lt;code&gt;x =&amp;gt; x.User.TenantId&lt;/code&gt; is not just executable C# code, but it is a data structure that represents the navigation from a &lt;code&gt;Post&lt;/code&gt; (&lt;code&gt;x&lt;/code&gt;), through its &lt;code&gt;User&lt;/code&gt; property, to that user&#39;s &lt;code&gt;TenantId&lt;/code&gt;. Linq2Db is designed to parse these expression trees and convert them into the corresponding SQL, which in this case involves an &lt;code&gt;INNER JOIN&lt;/code&gt; to the &lt;code&gt;user&lt;/code&gt; table.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;&lt;code&gt;[ExpressionMethod(nameof(GetTenantIdExpression))]&lt;/code&gt;&lt;/strong&gt;: This Linq2Db attribute is the magic that connects the property to the expression tree. When Linq2Db encounters &lt;code&gt;post.TenantId&lt;/code&gt; inside a LINQ query (like the one in our &lt;code&gt;ITenanted.Filter&lt;/code&gt; method), this attribute instructs it to substitute the SQL translation of the &lt;code&gt;GetTenantIdExpression&lt;/code&gt; method&#39;s return value.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;&lt;code&gt;public Ulid TenantId =&amp;gt; GetTenantIdExpression().Compile()(this);&lt;/code&gt;&lt;/strong&gt;: This line handles the case where the &lt;code&gt;TenantId&lt;/code&gt; property is accessed on an already-materialized &lt;code&gt;Post&lt;/code&gt; object in C# memory, outside of a database query. In this scenario, Linq2Db is not involved, and the &lt;code&gt;[ExpressionMethod]&lt;/code&gt; attribute has no effect. We must provide a valid C# implementation. We take our expression tree, &lt;code&gt;.Compile()&lt;/code&gt; it into a runnable delegate, and then invoke it for the current &lt;code&gt;Post&lt;/code&gt; instance (&lt;code&gt;this&lt;/code&gt;). For performance-critical code, this compiled delegate could be cached, but we have kept it simple for clarity.&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;This elegant solution allows the &lt;code&gt;TenantId&lt;/code&gt; property to work seamlessly both within database queries (translating to SQL) and on in-memory objects (executing as C# code).&lt;/p&gt;&lt;h2 id=&quot;5.-observing-composable-filters-in-action&quot; tabindex=&quot;-1&quot;&gt;5. Observing Composable Filters in Action&lt;/h2&gt;&lt;p&gt;We have now implemented all the necessary components. The &lt;code&gt;ITenanted&lt;/code&gt; interface defines the filter, the scaffolder applies it to entities with a direct &lt;code&gt;TenantId&lt;/code&gt;, and we have manually implemented it for the &lt;code&gt;Post&lt;/code&gt; entity using a projected property.&lt;/p&gt;&lt;p&gt;Let&#39;s verify that it all works together. We add a simple query to our application&#39;s entry point and inspect the generated SQL.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;File: &lt;code&gt;App/Program.cs&lt;/code&gt; (Addition)&lt;/strong&gt;&lt;/p&gt;&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code class=&quot;language-csharp&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// Set DB Context parameters&lt;/span&gt; ctx&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Attributes &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt; tenant&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Id &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// ...&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// Get posts that are tenanted through a user&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; ctx&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token generic-method&quot;&gt;&lt;span class=&quot;token function&quot;&gt;GetTable&lt;/span&gt;&lt;span class=&quot;token generic class-name&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;Post&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;ToListAsync&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; Console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;WriteLine&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;ctx&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;LastQuery&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Executing this code produces the following SQL:&lt;/p&gt;&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;x&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;id&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;x&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;user_id&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;x&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;title&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;x&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;content&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;x&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;created_at&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;x&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;modified_at&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;x&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;removed_at&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;post&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;x&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;INNER&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;JOIN&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;a_User&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;ON&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;x&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;user_id&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;a_User&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;id&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;WHERE&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;a_User&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;removed_at&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;IS&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;NULL&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;AND&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;a_User&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;tenant_id&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; X&lt;span class=&quot;token string&quot;&gt;&#39;0199DD7C56CCCB4521596E010C8BED67&#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;AND&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;x&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;removed_at&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;IS&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;NULL&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This output is a perfect confirmation of our architecture&#39;s capabilities. A simple call to &lt;code&gt;ToListAsync()&lt;/code&gt; on the &lt;code&gt;Post&lt;/code&gt; table has triggered a cascade of automated, composed filters:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;&lt;code&gt;INNER JOIN [user] [a_User] ...&lt;/code&gt;&lt;/strong&gt;:&lt;br/&gt;Linq2Db generated this join automatically because the &lt;code&gt;ITenanted&lt;/code&gt; filter on &lt;code&gt;Post&lt;/code&gt; required access to &lt;code&gt;User.TenantId&lt;/code&gt; via our &lt;code&gt;ExpressionMethod&lt;/code&gt;.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;&lt;code&gt;WHERE ... [a_User].[tenant_id] = ...&lt;/code&gt;&lt;/strong&gt;:&lt;br/&gt;This is our new tenancy filter in action, correctly applied to the joined &lt;code&gt;user&lt;/code&gt; table.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;&lt;code&gt;WHERE ... [x].[removed_at] IS NULL&lt;/code&gt;&lt;/strong&gt;:&lt;br/&gt;The soft-delete filter for the &lt;code&gt;Post&lt;/code&gt; entity is present, as expected from our previous work.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;&lt;code&gt;WHERE ... [a_User].[removed_at] IS NULL&lt;/code&gt;&lt;/strong&gt;:&lt;br/&gt;The soft-delete filter for the &lt;code&gt;User&lt;/code&gt; entity has &lt;em&gt;also&lt;/em&gt; been applied to the joined table. Our system correctly aggregates filters for all entities involved in the query.&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;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 &lt;code&gt;WHERE&lt;/code&gt; clause, ensuring data integrity and security automatically.&lt;/p&gt;&lt;h2 id=&quot;conclusion&quot; tabindex=&quot;-1&quot;&gt;Conclusion&lt;/h2&gt;&lt;p&gt;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 Linq2Db&#39;s &lt;code&gt;ExpressionMethod&lt;/code&gt;, 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.&lt;/p&gt;&lt;p&gt;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.&lt;/p&gt;&lt;p&gt;You can review the complete implementation for this stage of the project on GitHub: &lt;a href=&quot;https://github.com/ByteAether/EnterpriseDal/tree/part5&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;https://github.com/ByteAether/EnterpriseDal/tree/part5&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;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.&lt;/p&gt;</content></entry><entry><title>Building an Enterprise Data Access Layer: Automated Soft-Delete</title><link href="https://byteaether.github.io/2025/building-an-enterprise-data-access-layer-automated-soft-delete/"/><updated>2025-11-04T00:00:00Z</updated><id>https://byteaether.github.io/2025/building-an-enterprise-data-access-layer-automated-soft-delete/</id><content type="html">&lt;p&gt;Welcome to the fourth installment of our series on building a robust, enterprise-grade Data Access Layer (DAL) in C#. In our &lt;a href=&quot;https://byteaether.github.io/2025/building-an-enterprise-data-access-layer-automated-auditing/&quot;&gt;previous post&lt;/a&gt;, we established a powerful automated auditing system to track entity creation and modification times (&lt;code&gt;CreatedAt&lt;/code&gt;/&lt;code&gt;ModifiedAt&lt;/code&gt;). Now, we will tackle another critical enterprise requirement: &lt;strong&gt;soft-delete&lt;/strong&gt;.&lt;/p&gt;&lt;p&gt;In many systems, physically deleting data (&lt;code&gt;DELETE FROM ...&lt;/code&gt;) is unacceptable. Regulatory compliance, audit trails, and the simple need for data recovery demand that records are preserved, even when a user &amp;quot;deletes&amp;quot; them. The standard solution is soft-deletion, where a record is marked as inactive instead of being removed.&lt;/p&gt;&lt;p&gt;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.&lt;/p&gt;&lt;p&gt;The complete code for this part of the series is available on our GitHub repository: &lt;a href=&quot;https://github.com/ByteAether/EnterpriseDal/tree/part4&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;https://github.com/ByteAether/EnterpriseDal/tree/part4&lt;/a&gt;.&lt;/p&gt;&lt;h2 id=&quot;the-soft-delete-contract%3A-the-iremovable-interface&quot; tabindex=&quot;-1&quot;&gt;The Soft-Delete Contract: The &lt;code&gt;IRemovable&lt;/code&gt; Interface&lt;/h2&gt;&lt;p&gt;Consistent with our architecture, we define entity behaviors through interfaces. For soft-delete, we introduce the &lt;code&gt;IRemovable&lt;/code&gt; interface in the &lt;code&gt;DAL.Base/EntityBehavior&lt;/code&gt; namespace.&lt;/p&gt;&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code class=&quot;language-csharp&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;namespace&lt;/span&gt; &lt;span class=&quot;token namespace&quot;&gt;DAL&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Base&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;EntityBehavior&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token generic-method&quot;&gt;&lt;span class=&quot;token function&quot;&gt;EntityFilter&lt;/span&gt;&lt;span class=&quot;token generic class-name&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;IRemovable&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;nameof&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;Filter&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;interface&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;IRemovable&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token type-list&quot;&gt;&lt;span class=&quot;token class-name&quot;&gt;IEntity&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token return-type class-name&quot;&gt;DateTime&lt;span class=&quot;token punctuation&quot;&gt;?&lt;/span&gt;&lt;/span&gt; RemovedAt &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;token return-type class-name&quot;&gt;IQueryable&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;T&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token generic-method&quot;&gt;&lt;span class=&quot;token function&quot;&gt;Filter&lt;/span&gt;&lt;span class=&quot;token generic class-name&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;T&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;IQueryable&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;T&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; q&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;IDbCtx&lt;/span&gt; _&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;where&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;T&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token type-list&quot;&gt;&lt;span class=&quot;token class-name&quot;&gt;IRemovable&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; q&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Where&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;x &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; x&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;RemovedAt &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;It introduces a nullable &lt;code&gt;RemovedAt&lt;/code&gt; property. When this property is &lt;code&gt;NULL&lt;/code&gt;, 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.&lt;/p&gt;&lt;p&gt;Our database schema has been updated accordingly, with a nullable &lt;code&gt;removed_at DATETIME&lt;/code&gt; column added to all tables.&lt;/p&gt;&lt;h2 id=&quot;the-core-challenge%3A-global-and-composable-query-filters&quot; tabindex=&quot;-1&quot;&gt;The Core Challenge: Global and Composable Query Filters&lt;/h2&gt;&lt;p&gt;Implementing soft-delete presents two significant challenges that a naive approach cannot solve:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;&lt;strong&gt;Ubiquitous Filtering:&lt;/strong&gt; Soft-deleted records must be hidden from &lt;em&gt;every&lt;/em&gt; query by default. Manually adding a &lt;code&gt;Where(x =&amp;gt; x.RemovedAt == null)&lt;/code&gt; 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.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Filtering Through Associations:&lt;/strong&gt; This is a more subtle but critical problem. Imagine fetching a &lt;code&gt;Post&lt;/code&gt; entity and then accessing its associated &lt;code&gt;Comments&lt;/code&gt; collection. The DAL must ensure that any soft-deleted comments are automatically filtered from that collection. A simple &lt;code&gt;WHERE&lt;/code&gt; clause on the initial &lt;code&gt;Post&lt;/code&gt; query will not propagate to lazily or eagerly loaded associations.&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;The solution is to use &lt;strong&gt;Global Query Filters&lt;/strong&gt;, a feature supported by ORMs like Linq2Db and Entity Framework Core. A global filter is a &lt;code&gt;WHERE&lt;/code&gt; condition that the ORM automatically appends to every query for a specific entity type.&lt;/p&gt;&lt;p&gt;However, these ORMs typically allow only a &lt;em&gt;single&lt;/em&gt; 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 &lt;code&gt;IRemovable&lt;/code&gt; and &lt;code&gt;ITenanted&lt;/code&gt;, requiring two separate filters to be applied simultaneously.&lt;/p&gt;&lt;h2 id=&quot;a-composable-architecture-for-global-filters&quot; tabindex=&quot;-1&quot;&gt;A Composable Architecture for Global Filters&lt;/h2&gt;&lt;p&gt;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 &lt;code&gt;DAL.Base/EntityFilter&lt;/code&gt; folder.&lt;/p&gt;&lt;p&gt;The system works in three main steps:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;Declaration:&lt;/strong&gt; We use a custom attribute, &lt;code&gt;[EntityFilter&amp;lt;T&amp;gt;(nameof(MethodName))]&lt;/code&gt;, to associate a filtering method with an interface or class. As seen in the &lt;code&gt;IRemovable&lt;/code&gt; interface code above, we declare that its filter logic is located in a static method named &lt;code&gt;Filter&lt;/code&gt;. This makes each behavioral interface self-contained.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;Discovery &amp;amp; Aggregation:&lt;/strong&gt; We created a helper that, upon database context initialization, scans all our DAL entities (classes implementing &lt;code&gt;IEntity&lt;/code&gt;). 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 &lt;code&gt;EntityFilter&lt;/code&gt; attribute. These individual expressions are then combined into a single, larger lambda expression using &lt;code&gt;Expression.AndAlso&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;Application:&lt;/strong&gt; The final, aggregated expression is then applied as a single global filter to the entity using Linq2Db&#39;s mapping schema. We trigger this entire process with a single call in our database context &lt;code&gt;DbCtx.InitDataContext()&lt;/code&gt;:&lt;/p&gt;&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code class=&quot;language-csharp&quot;&gt;&lt;span class=&quot;token range operator&quot;&gt;..&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt; MappingSchema&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token generic-method&quot;&gt;&lt;span class=&quot;token function&quot;&gt;ApplyEntityFilters&lt;/span&gt;&lt;span class=&quot;token generic class-name&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;DbCtx&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token range operator&quot;&gt;..&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;This architecture is incredibly flexible. It allows us to define filtering logic alongside the relevant behavior (like &lt;code&gt;IRemovable&lt;/code&gt; or, in the future, &lt;code&gt;ITenanted&lt;/code&gt;), and the DAL automatically composes these rules at runtime.&lt;/p&gt;&lt;h2 id=&quot;integrating-soft-delete-into-the-dal&quot; tabindex=&quot;-1&quot;&gt;Integrating Soft-Delete into the DAL&lt;/h2&gt;&lt;p&gt;With the filter aggregation system in place, we can now integrate the soft-delete logic.&lt;/p&gt;&lt;h4 id=&quot;1.-automated-scaffolding-(interceptor.cs)&quot; tabindex=&quot;-1&quot;&gt;1. Automated Scaffolding (&lt;code&gt;Interceptor.cs&lt;/code&gt;)&lt;/h4&gt;&lt;p&gt;Our custom scaffolding interceptor is extended to automatically add the &lt;code&gt;IRemovable&lt;/code&gt; interface to any generated entity partial class that has a corresponding &lt;code&gt;RemovedAt&lt;/code&gt; column in the database. This ensures our C# entity model stays perfectly in sync with our database-first philosophy.&lt;/p&gt;&lt;h4 id=&quot;2.-overriding-delete-operations-(crudextensions.cs)&quot; tabindex=&quot;-1&quot;&gt;2. Overriding Delete Operations (&lt;code&gt;CrudExtensions.cs&lt;/code&gt;)&lt;/h4&gt;&lt;p&gt;Next, we introduce new &lt;code&gt;RemoveAsync&lt;/code&gt; extension methods that replace direct calls to Linq2Db&#39;s &lt;code&gt;DeleteAsync&lt;/code&gt;. These methods contain the core soft-delete logic.&lt;/p&gt;&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code class=&quot;language-csharp&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;token return-type class-name&quot;&gt;Task&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token generic-method&quot;&gt;&lt;span class=&quot;token function&quot;&gt;RemoveAsync&lt;/span&gt;&lt;span class=&quot;token generic class-name&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;T&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;IQueryable&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;T&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; source&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;CancellationToken&lt;/span&gt; cancellationToken &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;where&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;T&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token type-list&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// Check if the entity type T implements IRemovable&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;typeof&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token type-expression class-name&quot;&gt;IRemovable&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;IsAssignableFrom&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;typeof&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token type-expression class-name&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// If so, build an UPDATE query instead of a DELETE&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; source &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Set&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;x &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;IRemovable&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;x&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;RemovedAt&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; DateTime&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;UtcNow&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;ModifyAsync&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;cancellationToken&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// Reuse our existing ModifyAsync&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// Otherwise, perform a hard delete&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; source&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;DeleteAsync&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;cancellationToken&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This implementation is transparent to the developer. When they call &lt;code&gt;RemoveAsync&lt;/code&gt; on a query for an &lt;code&gt;IRemovable&lt;/code&gt; entity, the DAL automatically translates it into an &lt;code&gt;UPDATE&lt;/code&gt; statement that sets the &lt;code&gt;RemovedAt&lt;/code&gt; timestamp. By cleverly chaining this into our existing &lt;code&gt;ModifyAsync&lt;/code&gt; method, we also get &lt;code&gt;ModifiedAt&lt;/code&gt; auditing for free.&lt;/p&gt;&lt;h2 id=&quot;verification%3A-seeing-it-in-action&quot; tabindex=&quot;-1&quot;&gt;Verification: Seeing It in Action&lt;/h2&gt;&lt;p&gt;Let&#39;s validate our implementation. We can write a simple test in &lt;code&gt;Program.cs&lt;/code&gt; to remove a user and inspect the generated SQL.&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;Code in &lt;code&gt;Program.cs&lt;/code&gt;:&lt;/strong&gt;&lt;/p&gt;&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code class=&quot;language-csharp&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// Assuming &#39;ctx&#39; is our DbCtx instance and &#39;u&#39; is a User entity&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; ctx&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token generic-method&quot;&gt;&lt;span class=&quot;token function&quot;&gt;GetTable&lt;/span&gt;&lt;span class=&quot;token generic class-name&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;User&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Where&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;x &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; x&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Id &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; u&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Id&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;RemoveAsync&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; Console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;WriteLine&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;ctx&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;LastQuery&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;Generated SQL Output:&lt;/strong&gt;&lt;/p&gt;&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;UPDATE&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;SET&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;removed_at&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;2025-10-10 10:30:31.029&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;modified_at&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;2025-10-10 10:30:31.029&#39;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;WHERE&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;removed_at&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;IS&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;NULL&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;AND&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;id&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; X&lt;span class=&quot;token string&quot;&gt;&#39;0199CDAC13F5AB8EF21DB4A49A36D2F5&#39;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;The output confirms our success. The &lt;code&gt;DELETE&lt;/code&gt; operation was transparently converted into an &lt;code&gt;UPDATE&lt;/code&gt; that sets both &lt;code&gt;removed_at&lt;/code&gt; and &lt;code&gt;modified_at&lt;/code&gt;. Crucially, notice the &lt;code&gt;WHERE [user].[removed_at] IS NULL&lt;/code&gt; clause. Our global filter is automatically applied even to the &lt;code&gt;UPDATE&lt;/code&gt; operation itself, ensuring we don&#39;t try to re-delete an already deleted record. Any subsequent &lt;code&gt;SELECT&lt;/code&gt; query for this user will now automatically fail to find it, as it will not satisfy the global filter.&lt;/p&gt;&lt;h2 id=&quot;conclusion-and-next-steps&quot; tabindex=&quot;-1&quot;&gt;Conclusion and Next Steps&lt;/h2&gt;&lt;p&gt;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.&lt;/p&gt;&lt;p&gt;The powerful filter system we built is now ready for its next major task: &lt;strong&gt;multi-tenancy&lt;/strong&gt;. 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.&lt;/p&gt;&lt;p&gt;For a deeper dive into the implementation, please explore the source code on our GitHub repository: &lt;a href=&quot;https://github.com/ByteAether/EnterpriseDal/tree/part4&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;https://github.com/ByteAether/EnterpriseDal/tree/part4&lt;/a&gt;.&lt;/p&gt;</content></entry><entry><title>Building an Enterprise Data Access Layer: Automated Auditing</title><link href="https://byteaether.github.io/2025/building-an-enterprise-data-access-layer-automated-auditing/"/><updated>2025-10-24T00:00:00Z</updated><id>https://byteaether.github.io/2025/building-an-enterprise-data-access-layer-automated-auditing/</id><content type="html">&lt;p&gt;In our &lt;a href=&quot;https://byteaether.github.io/2025/building-an-enterprise-data-access-layer-database-and-code-structure/&quot;&gt;previous posts&lt;/a&gt;, 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 &lt;a href=&quot;https://github.com/linq2db/linq2db&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Linq2Db&lt;/a&gt;, 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 &lt;a href=&quot;https://github.com/ByteAether/Ulid&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;ULIDs&lt;/a&gt; as our primary keys for their performance benefits.&lt;/p&gt;&lt;p&gt;This post will build directly on that foundation. We will &lt;a href=&quot;https://github.com/ByteAether/EnterpriseDal/tree/part3&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;implement&lt;/a&gt; one of the most common and critical DAL features: automated audit fields. Specifically, we will ensure that &lt;code&gt;CreatedAt&lt;/code&gt; and &lt;code&gt;ModifiedAt&lt;/code&gt; 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.&lt;/p&gt;&lt;h2 id=&quot;a-philosophical-shift%3A-technical-vs.-business-logical-crud&quot; tabindex=&quot;-1&quot;&gt;A Philosophical Shift: Technical vs. Business-Logical CRUD&lt;/h2&gt;&lt;p&gt;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 &lt;a href=&quot;https://en.wikipedia.org/wiki/Create,_read,_update_and_delete&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;&lt;strong&gt;CRUD&lt;/strong&gt; (Create, Read, Update, Delete)&lt;/a&gt; to describe data operations. However, these terms often conflate low-level database actions with high-level business logic.&lt;/p&gt;&lt;p&gt;To build a truly robust DAL, we must separate these concepts:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;Technical CRUD:&lt;/strong&gt; These are the raw database commands: &lt;code&gt;INSERT&lt;/code&gt;, &lt;code&gt;UPDATE&lt;/code&gt;, &lt;code&gt;DELETE&lt;/code&gt;. They represent the fundamental operations the database engine can perform. In our DAL, these will be the underlying methods provided by Linq2Db.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Business-Logical CRUD:&lt;/strong&gt; These are higher-level abstractions that represent business intent: &lt;code&gt;CREATE&lt;/code&gt;, &lt;code&gt;MODIFY&lt;/code&gt;, &lt;code&gt;REMOVE&lt;/code&gt;. A single business-logical operation might involve multiple technical operations, validation, and the application of cross-cutting concerns like auditing or security.&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;Our goal is to expose the business-logical methods (&lt;code&gt;CREATE&lt;/code&gt;, &lt;code&gt;MODIFY&lt;/code&gt;, &lt;code&gt;REMOVE&lt;/code&gt;) to the application&#39;s business logic layer, while discouraging the direct use of the technical methods (&lt;code&gt;INSERT&lt;/code&gt;, &lt;code&gt;UPDATE&lt;/code&gt;, &lt;code&gt;DELETE&lt;/code&gt;). 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.&lt;/p&gt;&lt;p&gt;For now, this separation is a matter of developer discipline. In a future post, we may explore building a &lt;strong&gt;custom Roslyn (static code) analyzer&lt;/strong&gt; 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.&lt;/p&gt;&lt;h2 id=&quot;step-1%3A-defining-behavior-with-icreatable-and-imodifiable&quot; tabindex=&quot;-1&quot;&gt;Step 1: Defining Behavior with &lt;code&gt;ICreatable&lt;/code&gt; and &lt;code&gt;IModifiable&lt;/code&gt;&lt;/h2&gt;&lt;p&gt;To apply auditing logic selectively, we first need a way to identify which entities require it. We achieve this by defining &amp;quot;entity behavior&amp;quot; interfaces. For this feature, we will add two new interfaces to our &lt;code&gt;DAL.Base/EntityBehavior&lt;/code&gt; directory:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;&lt;code&gt;ICreatable&lt;/code&gt;&lt;/strong&gt;: This interface signifies that an entity has a creation timestamp.&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code class=&quot;language-csharp&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;interface&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ICreatable&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token type-list&quot;&gt;&lt;span class=&quot;token class-name&quot;&gt;IEntity&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token return-type class-name&quot;&gt;DateTime&lt;/span&gt; CreatedAt &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;&lt;li&gt;&lt;strong&gt;&lt;code&gt;IModifiable&lt;/code&gt;&lt;/strong&gt;: This interface signifies that an entity has a modification timestamp.&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code class=&quot;language-csharp&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;interface&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;IModifiable&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token type-list&quot;&gt;&lt;span class=&quot;token class-name&quot;&gt;IEntity&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token return-type class-name&quot;&gt;DateTime&lt;/span&gt; ModifiedAt &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;These interfaces are simple contracts. An entity implementing &lt;code&gt;ICreatable&lt;/code&gt; guarantees it has a &lt;code&gt;CreatedAt&lt;/code&gt; property. This allows us to write generic extension methods that can operate on any entity fulfilling this contract.&lt;/p&gt;&lt;h2 id=&quot;step-2%3A-evolving-the-database-and-scaffolding&quot; tabindex=&quot;-1&quot;&gt;Step 2: Evolving the Database and Scaffolding&lt;/h2&gt;&lt;p&gt;Following our database-first approach, the next step is to update our database schema. We add &lt;code&gt;created_at DATETIME not null&lt;/code&gt; and &lt;code&gt;modified_at DATETIME not null&lt;/code&gt; columns to all four of our tables: &lt;code&gt;tenant&lt;/code&gt;, &lt;code&gt;user&lt;/code&gt;, &lt;code&gt;post&lt;/code&gt;, and &lt;code&gt;comment&lt;/code&gt;.&lt;/p&gt;&lt;p&gt;With the database schema updated, we can enhance our scaffolding interceptor. In addition to adding the &lt;code&gt;IEntity&lt;/code&gt; and &lt;code&gt;IIdentifiable&amp;lt;Ulid&amp;gt;&lt;/code&gt; interfaces as it did before, the interceptor will now inspect the properties of each table during code generation.&lt;/p&gt;&lt;ul&gt;&lt;li&gt;If a table contains a property named &lt;code&gt;CreatedAt&lt;/code&gt;, the interceptor will automatically add &lt;code&gt;ICreatable&lt;/code&gt; interface to the generated partial class definition.&lt;/li&gt;&lt;li&gt;If a table contains a property named &lt;code&gt;ModifiedAt&lt;/code&gt;, it will add &lt;code&gt;IModifiable&lt;/code&gt; interface.&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;After running the scaffolding tool, our generated &lt;code&gt;User&lt;/code&gt; entity, for example, will now have a signature like this in &lt;code&gt;DbCtx.generated.cs&lt;/code&gt;:&lt;/p&gt;&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code class=&quot;language-csharp&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;partial&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;User&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token type-list&quot;&gt;&lt;span class=&quot;token class-name&quot;&gt;IEntity&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;IIdentifiable&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;Ulid&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ICreatable&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;IModifiable&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// ... generated properties&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;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.&lt;/p&gt;&lt;h2 id=&quot;step-3%3A-implementing-the-business-logic-in-crudextensions.cs&quot; tabindex=&quot;-1&quot;&gt;Step 3: Implementing the Business Logic in &lt;code&gt;CrudExtensions.cs&lt;/code&gt;&lt;/h2&gt;&lt;p&gt;Now that our entities automatically implement the correct interfaces, we can create the extension methods that constitute our business-logical &lt;code&gt;CREATE&lt;/code&gt; and &lt;code&gt;MODIFY&lt;/code&gt; operations. We&#39;ll place these in a new file, &lt;a href=&quot;https://github.com/ByteAether/EnterpriseDal/blob/part3/DAL.Base/CrudExtensions.cs&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;&lt;code&gt;DAL.Base/CrudExtensions.cs&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;&lt;h3 id=&quot;the-createasync-method&quot; tabindex=&quot;-1&quot;&gt;The &lt;code&gt;CreateAsync&lt;/code&gt; Method&lt;/h3&gt;&lt;p&gt;The &lt;code&gt;CreateAsync&lt;/code&gt; method is an extension on &lt;code&gt;DbCtx&lt;/code&gt;. It takes either an entity object or an enumerable of them, automatically sets the audit fields, and then calls the underlying technical &lt;code&gt;INSERT&lt;/code&gt; operation.&lt;/p&gt;&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code class=&quot;language-csharp&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token return-type class-name&quot;&gt;Task&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;long&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token generic-method&quot;&gt;&lt;span class=&quot;token function&quot;&gt;CreateAsync&lt;/span&gt;&lt;span class=&quot;token generic class-name&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;T&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;DbCtx&lt;/span&gt; ctx&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;IEnumerable&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;T&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; entities&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;CancellationToken&lt;/span&gt; cancellationToken &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;where&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;T&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token type-list&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;IEntity&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; ctx&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;BulkCopyAsync&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt; entities&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Select&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;e &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;e &lt;span class=&quot;token keyword&quot;&gt;is&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ICreatable&lt;/span&gt; creatable &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; creatable&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;CreatedAt &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;default&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; creatable&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;CreatedAt &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; DateTime&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;UtcNow&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;e &lt;span class=&quot;token keyword&quot;&gt;is&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;IModifiable&lt;/span&gt; updateable &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; updateable&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ModifiedAt &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;default&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; updateable&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ModifiedAt &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; DateTime&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;UtcNow&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; e&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; cancellationToken &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;RowsCopied&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;token return-type class-name&quot;&gt;Task&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;long&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token generic-method&quot;&gt;&lt;span class=&quot;token function&quot;&gt;CreateAsync&lt;/span&gt;&lt;span class=&quot;token generic class-name&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;T&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;DbCtx&lt;/span&gt; ctx&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;T&lt;/span&gt; entity&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;CancellationToken&lt;/span&gt; cancellationToken &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;where&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;T&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token type-list&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;IEntity&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; ctx&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;CreateAsync&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token attribute&quot;&gt;&lt;span class=&quot;token class-name&quot;&gt;entity&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; cancellationToken&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;By checking the interfaces, these methods correctly handle any entity. If an entity only implements &lt;code&gt;ICreatable&lt;/code&gt;, only &lt;code&gt;CreatedAt&lt;/code&gt; is set. If it implements both, both timestamps are set.&lt;/p&gt;&lt;h3 id=&quot;the-modifyasync-method&quot; tabindex=&quot;-1&quot;&gt;The &lt;code&gt;ModifyAsync&lt;/code&gt; Method&lt;/h3&gt;&lt;p&gt;The &lt;code&gt;ModifyAsync&lt;/code&gt; methods are slightly different. They are extensions on either &lt;code&gt;DbCtx&lt;/code&gt; or &lt;code&gt;IQueryable&amp;lt;T&amp;gt;&lt;/code&gt;, designed to work with Linq2Db&#39;s fluent update syntax.&lt;/p&gt;&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code class=&quot;language-csharp&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;token return-type class-name&quot;&gt;Task&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token generic-method&quot;&gt;&lt;span class=&quot;token function&quot;&gt;ModifyAsync&lt;/span&gt;&lt;span class=&quot;token generic class-name&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;T&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;DbCtx&lt;/span&gt; ctx&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;T&lt;/span&gt; entity&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;CancellationToken&lt;/span&gt; cancellationToken &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;where&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;T&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token type-list&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;IEntity&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;entity &lt;span class=&quot;token keyword&quot;&gt;is&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;IModifiable&lt;/span&gt; updateable&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; updateable&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ModifiedAt &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; DateTime&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;UtcNow&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; ctx&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token generic-method&quot;&gt;&lt;span class=&quot;token function&quot;&gt;GetTable&lt;/span&gt;&lt;span class=&quot;token generic class-name&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;T&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;UpdateOptimisticAsync&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;entity&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; cancellationToken&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;token return-type class-name&quot;&gt;Task&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token generic-method&quot;&gt;&lt;span class=&quot;token function&quot;&gt;ModifyAsync&lt;/span&gt;&lt;span class=&quot;token generic class-name&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;T&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;IUpdatable&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;T&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; source&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;CancellationToken&lt;/span&gt; cancellationToken &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;where&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;T&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token type-list&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;typeof&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token type-expression class-name&quot;&gt;IModifiable&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;IsAssignableFrom&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;typeof&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token type-expression class-name&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; source &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; source&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Set&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt; x &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; Sql&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token generic-method&quot;&gt;&lt;span class=&quot;token function&quot;&gt;Property&lt;/span&gt;&lt;span class=&quot;token generic class-name&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;DateTime&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;x&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;nameof&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;IModifiable&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ModifiedAt&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; DateTime&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;UtcNow &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; source&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;UpdateAsync&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;cancellationToken&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;token return-type class-name&quot;&gt;Task&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token generic-method&quot;&gt;&lt;span class=&quot;token function&quot;&gt;ModifyAsync&lt;/span&gt;&lt;span class=&quot;token generic class-name&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;T&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;IQueryable&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;T&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; source&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;CancellationToken&lt;/span&gt; cancellationToken &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;where&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;T&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token type-list&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; source&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;AsUpdatable&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;ModifyAsync&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;cancellationToken&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;These methods cleverly inspect the generic type &lt;code&gt;T&lt;/code&gt;. If &lt;code&gt;T&lt;/code&gt; implements &lt;code&gt;IModifiable&lt;/code&gt;, it injects a &lt;code&gt;.Set()&lt;/code&gt; call to update the &lt;code&gt;ModifiedAt&lt;/code&gt; field with the current timestamp before executing the final &lt;code&gt;UpdateAsync()&lt;/code&gt; call or assigns the current timestamp to an entity&#39;s property. This ensures that any update operation expressed through this fluent API is properly audited.&lt;/p&gt;&lt;h3 id=&quot;a-note-on-iqueryexpressioninterceptor&quot; tabindex=&quot;-1&quot;&gt;A Note on &lt;code&gt;IQueryExpressionInterceptor&lt;/code&gt;&lt;/h3&gt;&lt;p&gt;You might be wondering if there is a more integrated way to achieve this within Linq2Db, rather than creating wrapper extension methods. Linq2Db provides a &lt;a href=&quot;https://github.com/linq2db/linq2db/blob/59940d23f474599b6820a83f3116cc256dd42cc1/Source/LinqToDB/Interceptors/IQueryExpressionInterceptor.cs&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;&lt;code&gt;IQueryExpressionInterceptor&lt;/code&gt;&lt;/a&gt; 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.&lt;/p&gt;&lt;p&gt;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 &lt;code&gt;INSERT&lt;/code&gt; operations or for calls to &lt;code&gt;IDataContext.UpdateAsync&amp;lt;T&amp;gt;()&lt;/code&gt;, although it does trigger for the fluent &lt;code&gt;IUpdatable&amp;lt;T&amp;gt;.UpdateAsync()&lt;/code&gt; method. This inconsistency makes it unsuitable for building a reliable, all-encompassing auditing system.&lt;/p&gt;&lt;p&gt;Should this behavior be fixed in a future version of Linq2Db 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 &lt;code&gt;InsertAsync&lt;/code&gt; and &lt;code&gt;UpdateAsync&lt;/code&gt; methods directly. This would eliminate the need for our custom &lt;code&gt;CreateAsync&lt;/code&gt; and &lt;code&gt;ModifyAsync&lt;/code&gt; 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.&lt;/p&gt;&lt;h2 id=&quot;seeing-it-in-action&quot; tabindex=&quot;-1&quot;&gt;Seeing it in Action&lt;/h2&gt;&lt;p&gt;With all the pieces in place, we can verify that our implementation works as expected. We can write a small test in &lt;code&gt;Program.cs&lt;/code&gt; to create and then modify a &lt;code&gt;User&lt;/code&gt; entity. Notice that our C# code never explicitly provides a value for &lt;code&gt;CreatedAt&lt;/code&gt; or &lt;code&gt;ModifiedAt&lt;/code&gt;.&lt;/p&gt;&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code class=&quot;language-csharp&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; ctx&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;BeginTransactionAsync&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// Create entity&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt;&lt;/span&gt; u &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token constructor-invocation class-name&quot;&gt;User&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; Id &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Ulid&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;New&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; Username &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;asd123&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; ctx&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;CreateAsync&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;u&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; Console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;WriteLine&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;ctx&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;LastQuery&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// Modify entity&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; ctx&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token generic-method&quot;&gt;&lt;span class=&quot;token function&quot;&gt;GetTable&lt;/span&gt;&lt;span class=&quot;token generic class-name&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;User&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Where&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;x &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; x&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Id &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; u&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Id&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Set&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;x &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; x&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Username&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;asd1234&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;ModifyAsync&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; Console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;WriteLine&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;ctx&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;LastQuery&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; ctx&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;RollbackTransactionAsync&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Executing this code produces the following SQL, captured from Linq2Db&#39;s &lt;code&gt;LastQuery&lt;/code&gt; property:&lt;/p&gt;&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;INSERT&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;INTO&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;id&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;tenant_id&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;username&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;created_at&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;modified_at&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;VALUES&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;X&lt;span class=&quot;token string&quot;&gt;&#39;0199C31CC424CFC7293147E8A6AE578C&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;X&lt;span class=&quot;token string&quot;&gt;&#39;00000000000000000000000000000000&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;asd123&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;2025-10-08 09:17:46.472&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;2025-10-08 09:17:46.473&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;UPDATE&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;SET&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;username&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;asd1234&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;modified_at&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;2025-10-08 09:17:46.506&#39;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;WHERE&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;id&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; X&lt;span class=&quot;token string&quot;&gt;&#39;0199C31CC424CFC7293147E8A6AE578C&#39;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The output confirms our success. The &lt;code&gt;INSERT&lt;/code&gt; statement correctly includes values for both &lt;code&gt;created_at&lt;/code&gt; and &lt;code&gt;modified_at&lt;/code&gt;. The &lt;code&gt;UPDATE&lt;/code&gt; statement automatically includes the &lt;code&gt;SET&lt;/code&gt; clause for &lt;code&gt;modified_at&lt;/code&gt;. Our DAL now handles these audit fields transparently, reliably, and without any developer effort.&lt;/p&gt;&lt;p&gt;The complete implementation for this part of the series can be found on GitHub: &lt;a href=&quot;https://github.com/ByteAether/EnterpriseDal/tree/part3&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;https://github.com/ByteAether/EnterpriseDal/tree/part3&lt;/a&gt;.&lt;/p&gt;&lt;h2 id=&quot;conclusion-and-next-steps&quot; tabindex=&quot;-1&quot;&gt;Conclusion and Next Steps&lt;/h2&gt;&lt;p&gt;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.&lt;/p&gt;&lt;p&gt;In the next episode, we will tackle another core DAL capability: &lt;strong&gt;Soft-Delete&lt;/strong&gt;. 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.&lt;/p&gt;</content></entry><entry><title>Building an Enterprise Data Access Layer: Database and Code Structure</title><link href="https://byteaether.github.io/2025/building-an-enterprise-data-access-layer-database-and-code-structure/"/><updated>2025-10-07T00:00:00Z</updated><id>https://byteaether.github.io/2025/building-an-enterprise-data-access-layer-database-and-code-structure/</id><content type="html">&lt;p&gt;Welcome back to our series on building a robust, enterprise-grade Data Access Layer (DAL) with C# and Linq2Db. In our &lt;a href=&quot;https://byteaether.github.io/2025/building-an-enterprise-data-access-layer-the-foundation/&quot;&gt;previous post&lt;/a&gt;, we established the core principles and non-negotiable capabilities our DAL must possess, including automatic multi-tenancy filtering, soft-delete, and auditing. Now, we&#39;ll begin laying the foundation for this powerful system by defining the database schema and the initial code structure.&lt;/p&gt;&lt;p&gt;This post will explore the deliberate technical choices we have made for this project&#39;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.&lt;/p&gt;&lt;h2 id=&quot;the-database-first-philosophy&quot; tabindex=&quot;-1&quot;&gt;The Database-First Philosophy&lt;/h2&gt;&lt;p&gt;Before we define the structure, let&#39;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.&lt;/p&gt;&lt;p&gt;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.&lt;/p&gt;&lt;h2 id=&quot;our-core-data-model&quot; tabindex=&quot;-1&quot;&gt;Our Core Data Model&lt;/h2&gt;&lt;p&gt;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.&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;code&gt;tenant&lt;/code&gt;: 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 &lt;code&gt;id&lt;/code&gt; and a &lt;code&gt;name&lt;/code&gt;.&lt;/li&gt;&lt;li&gt;&lt;code&gt;user&lt;/code&gt;: This table holds our user accounts. Each user is associated with a specific &lt;code&gt;tenant&lt;/code&gt; via a foreign key (&lt;code&gt;tenant_id&lt;/code&gt;). It also contains a &lt;code&gt;username&lt;/code&gt;.&lt;/li&gt;&lt;li&gt;&lt;code&gt;post&lt;/code&gt;: A &lt;code&gt;post&lt;/code&gt; is a content unit created by a &lt;code&gt;user&lt;/code&gt;. It references the &lt;code&gt;user&lt;/code&gt; via a foreign key (&lt;code&gt;user_id&lt;/code&gt;). A post has a &lt;code&gt;title&lt;/code&gt; and the main &lt;code&gt;content&lt;/code&gt;.&lt;/li&gt;&lt;li&gt;&lt;code&gt;comment&lt;/code&gt;: This table stores comments on a post. Each &lt;code&gt;comment&lt;/code&gt; is linked to a &lt;code&gt;post&lt;/code&gt; via a foreign key (&lt;code&gt;post_id&lt;/code&gt;) and contains its &lt;code&gt;content&lt;/code&gt;.&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;The relationships are as follows:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;code&gt;comment.post_id&lt;/code&gt; references &lt;code&gt;post.id&lt;/code&gt;&lt;/li&gt;&lt;li&gt;&lt;code&gt;post.user_id&lt;/code&gt; references &lt;code&gt;user.id&lt;/code&gt;&lt;/li&gt;&lt;li&gt;&lt;code&gt;user.tenant_id&lt;/code&gt; references &lt;code&gt;tenant.id&lt;/code&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;This simple structure allows us to demonstrate how the DAL can automatically enforce rules like multi-tenancy and permissions by navigating these relationships.&lt;/p&gt;&lt;h2 id=&quot;the-power-of-the-ulid&quot; tabindex=&quot;-1&quot;&gt;The Power of the ULID&lt;/h2&gt;&lt;p&gt;A cornerstone of our database design is the choice of &lt;strong&gt;&lt;a href=&quot;https://github.com/ulid/spec&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;ULID (Universal Unique Lexicographically Sortable Identifier)&lt;/a&gt;&lt;/strong&gt; 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.&lt;/p&gt;&lt;p&gt;In our C# code, we will use the &lt;a href=&quot;https://github.com/ByteAether/Ulid&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;ByteAether.Ulid&lt;/a&gt; library. This library provides a native &lt;code&gt;Ulid&lt;/code&gt; type that is specifically designed for this purpose. In the database, ULIDs are stored as 128-bit binary, which is the most efficient representation.&lt;/p&gt;&lt;p&gt;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.&lt;/p&gt;&lt;p&gt;For databases like SQLite, this property allows us to declare our tables &lt;code&gt;WITHOUT ROWID&lt;/code&gt;. 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.&lt;/p&gt;&lt;p&gt;Here is the full SQL schema for our four tables:&lt;/p&gt;&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;create&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;table&lt;/span&gt; tenant &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt; id ulid &lt;span class=&quot;token operator&quot;&gt;not&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;null&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;primary&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; name &lt;span class=&quot;token keyword&quot;&gt;VARCHAR&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;64&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;not&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;null&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;unique&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; without rowid&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;create&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;table&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;user&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt; id ulid &lt;span class=&quot;token operator&quot;&gt;not&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;null&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;primary&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; tenant_id ulid &lt;span class=&quot;token operator&quot;&gt;not&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;null&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;references&lt;/span&gt; tenant&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; username &lt;span class=&quot;token keyword&quot;&gt;VARCHAR&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;64&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;not&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;null&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; without rowid&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;create&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;table&lt;/span&gt; post &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt; id ulid &lt;span class=&quot;token operator&quot;&gt;not&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;null&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;primary&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; user_id ulid &lt;span class=&quot;token operator&quot;&gt;not&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;null&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;references&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; title &lt;span class=&quot;token keyword&quot;&gt;varchar&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;64&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;not&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; content &lt;span class=&quot;token keyword&quot;&gt;text&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;not&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;null&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; without rowid&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;create&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;table&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;comment&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt; id ulid &lt;span class=&quot;token operator&quot;&gt;not&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;null&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;primary&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; post_id ulid &lt;span class=&quot;token operator&quot;&gt;not&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;null&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;references&lt;/span&gt; post&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; content &lt;span class=&quot;token keyword&quot;&gt;text&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;not&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;null&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; without rowid&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&quot;the-scaffolding-and-code-structure&quot; tabindex=&quot;-1&quot;&gt;The Scaffolding and Code Structure&lt;/h2&gt;&lt;p&gt;With our database schema defined, we now turn to the C# code. We will use &lt;a href=&quot;https://linq2db.github.io/articles/CLI.html&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Linq2Db&#39;s built-in scaffolding tool&lt;/a&gt; to generate our C# entity classes and a database context. However, we will enhance this process with a custom scaffolding interceptor.&lt;/p&gt;&lt;p&gt;&lt;a href=&quot;https://github.com/ByteAether/EnterpriseDal/tree/part2&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Our core code structure&lt;/a&gt; will be organized into a few key areas:&lt;/p&gt;&lt;h3 id=&quot;dal.base&quot; tabindex=&quot;-1&quot;&gt;&lt;code&gt;DAL.Base&lt;/code&gt;&lt;/h3&gt;&lt;p&gt;This namespace holds the fundamental interfaces that all of our entities may inherit from. We&#39;re keeping it simple for now, with two interfaces:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;code&gt;IEntity&lt;/code&gt;: 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.&lt;/li&gt;&lt;li&gt;&lt;code&gt;IIdentifiable&amp;lt;T&amp;gt;&lt;/code&gt;: An interface that marks any entity that has a property named &lt;code&gt;Id&lt;/code&gt; of type &lt;code&gt;T&lt;/code&gt;. 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 &lt;code&gt;queryable.WhereId()&lt;/code&gt; method would require this interface.&lt;/li&gt;&lt;/ul&gt;&lt;h3 id=&quot;dal.context&quot; tabindex=&quot;-1&quot;&gt;&lt;code&gt;DAL.Context&lt;/code&gt;&lt;/h3&gt;&lt;p&gt;This namespace contains our database context and all generated entity classes. We&#39;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.&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;code&gt;DbCtx.generated.cs&lt;/code&gt;: This file will be created by the Linq2Db scaffolding tool. It will contain our &lt;code&gt;DbCtx&lt;/code&gt; class, as well as partial classes for our &lt;code&gt;Tenant&lt;/code&gt;, &lt;code&gt;User&lt;/code&gt;, &lt;code&gt;Post&lt;/code&gt;, and &lt;code&gt;Comment&lt;/code&gt; entities.&lt;/li&gt;&lt;li&gt;&lt;code&gt;DbCtx.cs&lt;/code&gt;: This is the complementary file for our &lt;code&gt;DbCtx.generated.cs&lt;/code&gt;. The &lt;code&gt;DbCtx&lt;/code&gt; class is a partial class, so we can extend it with our own methods and properties here, leaving the generated file untouched.&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;A key piece of the &lt;code&gt;DbCtx.cs&lt;/code&gt; file is the &lt;code&gt;partial void InitDataContext()&lt;/code&gt; method. This method, which is automatically called by the generated constructor in &lt;code&gt;DbCtx.generated.cs&lt;/code&gt;, is where we will define the runtime type mapping for our &lt;code&gt;Ulid&lt;/code&gt;s. This ensures that when Linq2Db reads a &lt;code&gt;ulid&lt;/code&gt; type from the database (as a byte array), it is correctly converted into a &lt;code&gt;Ulid&lt;/code&gt; C# object, and vice versa. This runtime mapping is the counterpart to the design-time mapping handled by our &lt;code&gt;ScaffoldInterceptor&lt;/code&gt;.&lt;/p&gt;&lt;h3 id=&quot;dal.context.entity&quot; tabindex=&quot;-1&quot;&gt;&lt;code&gt;DAL.Context.Entity&lt;/code&gt;&lt;/h3&gt;&lt;p&gt;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 &lt;code&gt;Post.cs&lt;/code&gt; file to add a convenience method for getting the post&#39;s user from the database.&lt;/p&gt;&lt;h3 id=&quot;dal.scaffoldinterceptor&quot; tabindex=&quot;-1&quot;&gt;&lt;code&gt;DAL.ScaffoldInterceptor&lt;/code&gt;&lt;/h3&gt;&lt;p&gt;This is the &amp;quot;secret sauce&amp;quot; of our scaffolding process. We will create a class that implements &lt;code&gt;ScaffoldInterceptors&lt;/code&gt;. This interceptor allows us to hook into the scaffolding process and customize it.&lt;/p&gt;&lt;p&gt;Our custom interceptor will perform two key functions:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;&lt;strong&gt;Interface Injection&lt;/strong&gt;: It will automatically add our &lt;code&gt;IEntity&lt;/code&gt; and &lt;code&gt;IIdentifiable&amp;lt;Ulid&amp;gt;&lt;/code&gt; interfaces to the generated partial entity classes.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;ULID Type Mapping&lt;/strong&gt;: It will intercept any database column with the &lt;code&gt;ulid&lt;/code&gt; type and ensure it is properly mapped to the &lt;code&gt;Ulid&lt;/code&gt; type from our C# library in the generated code.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Association Naming Convention&lt;/strong&gt;: 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 &#39;s&#39; suffix (e.g., a &lt;code&gt;Post&lt;/code&gt; entity will have a property named &lt;code&gt;Comments&lt;/code&gt;, not &lt;code&gt;Commentaries&lt;/code&gt; or &lt;code&gt;Commentss&lt;/code&gt;). This deliberate choice avoids the ambiguity and confusion that can arise from irregular pluralization in English (e.g., avoiding a scenario where a &lt;code&gt;Mouse&lt;/code&gt; entity might generate a &lt;code&gt;Mice&lt;/code&gt; collection). For Many-To-One relationships, we simply use the entity&#39;s singular name (e.g., a &lt;code&gt;Comment&lt;/code&gt; entity will have a &lt;code&gt;Post&lt;/code&gt; property).&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;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.&lt;/p&gt;&lt;h2 id=&quot;looking-ahead&quot; tabindex=&quot;-1&quot;&gt;Looking Ahead&lt;/h2&gt;&lt;p&gt;In this post, we&#39;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&#39;s root also includes a simple &lt;code&gt;Program.cs&lt;/code&gt; 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 &lt;em&gt;generated SQL&lt;/em&gt; 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.&lt;/p&gt;&lt;p&gt;The full source code for this post, including the scaffolding interceptor and the completed project structure, is available at &lt;a href=&quot;https://github.com/ByteAether/EnterpriseDal/tree/part2&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;GitHub&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;In the next post, we will dive into the details of implementing the crucial audit fields (&lt;code&gt;CreatedAt&lt;/code&gt;, &lt;code&gt;CreatedBy&lt;/code&gt;, &lt;code&gt;ModifiedAt&lt;/code&gt;, and &lt;code&gt;ModifiedBy&lt;/code&gt;) and the logic to automatically populate them during data operations. We&#39;ll show how the foundation we&#39;ve built here makes this complex, cross-cutting concern remarkably simple to implement.&lt;/p&gt;</content></entry><entry><title>Building an Enterprise Data Access Layer: The Foundation</title><link href="https://byteaether.github.io/2025/building-an-enterprise-data-access-layer-the-foundation/"/><updated>2025-09-25T00:00:00Z</updated><id>https://byteaether.github.io/2025/building-an-enterprise-data-access-layer-the-foundation/</id><content type="html">&lt;p&gt;In enterprise software development, the &lt;strong&gt;Data Access Layer (DAL)&lt;/strong&gt; serves as the critical interface between an application&#39;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.&lt;/p&gt;&lt;p&gt;This series will detail the design and implementation of an enterprise-level Data Access Layer in C#, with a primary focus on &lt;a href=&quot;https://github.com/linq2db/linq2db&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Linq2Db&lt;/a&gt;. We&#39;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. Linq2Db&#39;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.&lt;/p&gt;&lt;h2 id=&quot;the-necessity-of-an-automated%2C-error-resistant-dal&quot; tabindex=&quot;-1&quot;&gt;The Necessity of an Automated, Error-Resistant DAL&lt;/h2&gt;&lt;p&gt;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:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;Data Inconsistencies:&lt;/strong&gt; Records may lack complete audit information, entities might be inconsistently soft-deleted, or multi-tenancy rules may be applied unevenly.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Security Vulnerabilities:&lt;/strong&gt; A missed &lt;code&gt;TenantId&lt;/code&gt; filter or inadequate enforcement of row-level permissions can result in unauthorized data exposure. Such breaches carry significant legal, financial, and reputational risks.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Increased Development Overhead and Technical Debt:&lt;/strong&gt; Manual implementation of cross-cutting concerns introduces boilerplate code, extends development cycles, and increases the probability of introducing subtle, hard-to-diagnose defects.&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;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.&lt;/p&gt;&lt;h2 id=&quot;core-capabilities-of-an-enterprise-data-access-layer&quot; tabindex=&quot;-1&quot;&gt;Core Capabilities of an Enterprise Data Access Layer&lt;/h2&gt;&lt;p&gt;We&#39;ll now examine the critical capabilities an enterprise DAL should provide, abstracting them from the application&#39;s business logic.&lt;/p&gt;&lt;h3 id=&quot;1.-createdat%2Fmodifiedat-auditing&quot; tabindex=&quot;-1&quot;&gt;1. &lt;code&gt;CreatedAt&lt;/code&gt;/&lt;code&gt;ModifiedAt&lt;/code&gt; Auditing&lt;/h3&gt;&lt;p&gt;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 &lt;code&gt;CreatedAt&lt;/code&gt; and &lt;code&gt;ModifiedAt&lt;/code&gt; timestamps in the business logic for every &lt;code&gt;INSERT&lt;/code&gt; and &lt;code&gt;UPDATE&lt;/code&gt; operation is repetitive and prone to omission.&lt;/p&gt;&lt;p&gt;A sophisticated DAL should automate this process. Upon entity insertion, the DAL should automatically populate its &lt;code&gt;CreatedAt&lt;/code&gt; property with the current timestamp. Similarly, upon any update to an existing entity, the DAL should automatically update its &lt;code&gt;ModifiedAt&lt;/code&gt; 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.&lt;/p&gt;&lt;h3 id=&quot;2.-createdby%2Fmodifiedby-auditing&quot; tabindex=&quot;-1&quot;&gt;2. &lt;code&gt;CreatedBy&lt;/code&gt;/&lt;code&gt;ModifiedBy&lt;/code&gt; Auditing&lt;/h3&gt;&lt;p&gt;Extending temporal auditing, it is often critical to identify the user responsible for a particular data change. This &amp;quot;who&amp;quot; information, typically represented by a User ID, enhances accountability and traceability within the audit trail. As with &lt;code&gt;CreatedAt&lt;/code&gt;/&lt;code&gt;ModifiedAt&lt;/code&gt;, manually populating &lt;code&gt;CreatedBy&lt;/code&gt; and &lt;code&gt;ModifiedBy&lt;/code&gt; fields in the business logic is a source of potential errors.&lt;/p&gt;&lt;p&gt;The enterprise DAL should seamlessly manage this. For new entities, it should automatically set the &lt;code&gt;CreatedBy&lt;/code&gt; property to the ID of the current user. For updates, it should update the &lt;code&gt;ModifiedBy&lt;/code&gt; 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.&lt;/p&gt;&lt;h3 id=&quot;3.-soft-delete&quot; tabindex=&quot;-1&quot;&gt;3. Soft-Delete&lt;/h3&gt;&lt;p&gt;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 &amp;quot;deleted&amp;quot; rather than physically removing it.&lt;/p&gt;&lt;p&gt;An enterprise DAL should automate this process. When the business logic initiates a &amp;quot;delete&amp;quot; operation, the DAL should intercept this call. Instead of issuing a &lt;code&gt;DELETE&lt;/code&gt; SQL command, it should update a designated &lt;code&gt;DeletedAt&lt;/code&gt; property (e.g., a &lt;code&gt;DateTimeOffset?&lt;/code&gt; nullable timestamp) on the entity. Furthermore, all standard read operations performed through the DAL must automatically filter out entities where &lt;code&gt;DeletedAt&lt;/code&gt; 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 &lt;code&gt;WHERE DeletedAt IS NULL&lt;/code&gt; clauses in every query. This capability is essential for data integrity and provides a safeguard against accidental data loss.&lt;/p&gt;&lt;h3 id=&quot;4.-tenantid-filtering-(multi-tenancy)&quot; tabindex=&quot;-1&quot;&gt;4. &lt;code&gt;TenantId&lt;/code&gt; Filtering (Multi-Tenancy)&lt;/h3&gt;&lt;p&gt;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: &lt;code&gt;Tenant A&lt;/code&gt; must never access &lt;code&gt;Tenant B&lt;/code&gt;&#39;s data. Enforcing this isolation is exceptionally challenging if left to the business logic, as every query, join, and relationship traversal would require manual &lt;code&gt;TenantId&lt;/code&gt; filtering. A single missed filter can lead to catastrophic data leakage.&lt;/p&gt;&lt;p&gt;Our enterprise DAL must provide automatic &lt;code&gt;TenantId&lt;/code&gt; filtering. For entities designated as &amp;quot;tenant-owned,&amp;quot; the DAL should automatically inject a &lt;code&gt;WHERE TenantId = [CurrentTenantId]&lt;/code&gt; 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 &lt;code&gt;TenantId&lt;/code&gt; filter is applied appropriately at each relevant level to prevent data from other tenants from being inadvertently included. Similar to the &lt;code&gt;CreatedBy&lt;/code&gt;/&lt;code&gt;ModifiedBy&lt;/code&gt; capability, the &lt;code&gt;TenantId&lt;/code&gt; 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.&lt;/p&gt;&lt;h3 id=&quot;5.-entity-based-permissions-(row-level-security)&quot; tabindex=&quot;-1&quot;&gt;5. Entity-Based-Permissions (Row-Level Security)&lt;/h3&gt;&lt;p&gt;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.&lt;/p&gt;&lt;p&gt;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.&lt;/p&gt;&lt;h3 id=&quot;6.-projected-data-support-for-contextual-filtering&quot; tabindex=&quot;-1&quot;&gt;6. Projected Data Support for Contextual Filtering&lt;/h3&gt;&lt;p&gt;Both &lt;code&gt;TenantId&lt;/code&gt; filtering and entity-based permissions introduce a common challenge: the relevant ID (e.g., &lt;code&gt;TenantId&lt;/code&gt; or permission &lt;code&gt;ObjectId&lt;/code&gt;) may not always be a direct property on the entity being queried. For instance, a &lt;code&gt;Comment&lt;/code&gt; entity might not have its own &lt;code&gt;TenantId&lt;/code&gt; but instead inherits it from its parent &lt;code&gt;Post&lt;/code&gt; entity (&lt;code&gt;this.Post.TenantId&lt;/code&gt;). Similarly, permissions for a &lt;code&gt;Task&lt;/code&gt; might be based on the &lt;code&gt;Id&lt;/code&gt; of its &lt;code&gt;Project&lt;/code&gt; (&lt;code&gt;this.Project.Id&lt;/code&gt;).&lt;/p&gt;&lt;p&gt;The enterprise DAL must provide a mechanism for entities to define how these contextual IDs are retrieved. This &amp;quot;projected data support&amp;quot; allows an entity to specify a path or a function (e.g., a LINQ expression) that the DAL can use to resolve the &lt;code&gt;TenantId&lt;/code&gt; or the &lt;code&gt;ObjectId&lt;/code&gt; 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.&lt;/p&gt;&lt;h2 id=&quot;why-linq2db%3F&quot; tabindex=&quot;-1&quot;&gt;Why Linq2Db?&lt;/h2&gt;&lt;p&gt;While &lt;strong&gt;Entity Framework Core (EF Core)&lt;/strong&gt; is a widely adopted and powerful ORM in the .NET ecosystem, this series will primarily focus on implementing our enterprise DAL using &lt;strong&gt;&lt;a href=&quot;https://github.com/linq2db/linq2db&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Linq2Db&lt;/a&gt;&lt;/strong&gt;. Linq2Db offers a highly flexible and performant approach to data access, particularly excelling in scenarios requiring fine-grained control over generated SQL.&lt;/p&gt;&lt;p&gt;One of Linq2Db&#39;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 Linq2Db&#39;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, Linq2Db provides many of these features out-of-the-box, simplifying our implementation journey.&lt;/p&gt;&lt;p&gt;Our decision to focus on Linq2Db 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.&lt;/p&gt;&lt;h2 id=&quot;scope-of-history-support&quot; tabindex=&quot;-1&quot;&gt;Scope of History Support&lt;/h2&gt;&lt;p&gt;It is important to clarify that while this series will implement certain auditing functionalities, such as &lt;code&gt;CreatedAt&lt;/code&gt;/&lt;code&gt;ModifiedAt&lt;/code&gt; and &lt;code&gt;CreatedBy&lt;/code&gt;/&lt;code&gt;ModifiedBy&lt;/code&gt; fields, it will &lt;strong&gt;not&lt;/strong&gt; 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.&lt;/p&gt;&lt;h2 id=&quot;the-journey-ahead&quot; tabindex=&quot;-1&quot;&gt;The Journey Ahead&lt;/h2&gt;&lt;p&gt;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 Linq2Db as our primary ORM for this implementation journey, the business logic can focus solely on its core rules rather than on data access mechanics.&lt;/p&gt;&lt;p&gt;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 Linq2Db. Upon completion of this series, a comparative analysis may also be provided by reproducing the same solution using Entity Framework Core.&lt;/p&gt;</content></entry></feed>