<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: rinat kozin</title>
    <description>The latest articles on DEV Community by rinat kozin (@rinat_kozin_d0a2ef43e7824).</description>
    <link>https://dev.to/rinat_kozin_d0a2ef43e7824</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3929984%2Fc2f7b685-01dd-469b-aa6e-392a04a18376.webp</url>
      <title>DEV Community: rinat kozin</title>
      <link>https://dev.to/rinat_kozin_d0a2ef43e7824</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/rinat_kozin_d0a2ef43e7824"/>
    <language>en</language>
    <item>
      <title>Enterprise Integration Patterns in .NET, the deep-dive series — Part 1: the four in-memory channels (and the Exchange they carry)</title>
      <dc:creator>rinat kozin</dc:creator>
      <pubDate>Mon, 01 Jun 2026 21:51:33 +0000</pubDate>
      <link>https://dev.to/rinat_kozin_d0a2ef43e7824/enterprise-integration-patterns-in-net-the-deep-dive-series-part-1-the-four-in-memory-channels-3e24</link>
      <guid>https://dev.to/rinat_kozin_d0a2ef43e7824/enterprise-integration-patterns-in-net-the-deep-dive-series-part-1-the-four-in-memory-channels-3e24</guid>
      <description>&lt;p&gt;The earlier posts were the tour: what redb.Route is, an Apache Camel-style ESB for .NET — a fluent C# integration DSL with &lt;strong&gt;22 connector projects&lt;/strong&gt; (~30 URI schemes once you count the &lt;code&gt;https&lt;/code&gt;/&lt;code&gt;wss&lt;/code&gt;/&lt;code&gt;es&lt;/code&gt; variants), &lt;strong&gt;~30 Enterprise Integration Patterns&lt;/strong&gt; implemented natively across &lt;strong&gt;41 processors&lt;/strong&gt;, &lt;strong&gt;8 in-process components&lt;/strong&gt;, a compiled expression engine, and a pluggable marshal/unmarshal layer. Enough touring.&lt;/p&gt;

&lt;p&gt;This starts a &lt;strong&gt;deep-dive series&lt;/strong&gt; — one piece at a time, the actual DSL, the actual semantics, the gotchas that bite you the first time. Not a feature list. A working manual. The series runs on four parallel tracks; you can follow whichever maps to the problem in front of you.&lt;/p&gt;

&lt;h3&gt;
  
  
  Track A — the in-process foundation (8 components)
&lt;/h3&gt;

&lt;p&gt;The wires and the message model everything else stands on. Small surface, deep behavior.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;The four channels + the &lt;code&gt;Exchange&lt;/code&gt;&lt;/strong&gt; — &lt;code&gt;direct&lt;/code&gt;, &lt;code&gt;direct-vm&lt;/code&gt;, &lt;code&gt;seda&lt;/code&gt;, &lt;code&gt;vm&lt;/code&gt;, and the object they carry. &lt;em&gt;(this post — the foundation)&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scheduling &amp;amp; test components&lt;/strong&gt; — &lt;code&gt;timer&lt;/code&gt;, plus &lt;code&gt;quartz&lt;/code&gt;/&lt;code&gt;cron&lt;/code&gt; built on the same idea, and the &lt;code&gt;log&lt;/code&gt; / &lt;code&gt;mock&lt;/code&gt; / &lt;code&gt;validator&lt;/code&gt; components you'll actually lean on in tests.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Track B — the Enterprise Integration Patterns (~30, across 41 processors)
&lt;/h3&gt;

&lt;p&gt;Grouped the way Hohpe &amp;amp; Woolf's &lt;em&gt;Enterprise Integration Patterns&lt;/em&gt; groups them, one article per cluster, each dissected against the shipped processor. (The codebase has 41 processors total; a handful — &lt;code&gt;To&lt;/code&gt;, &lt;code&gt;Log&lt;/code&gt;, &lt;code&gt;Delegate&lt;/code&gt;, &lt;code&gt;RoutePolicy&lt;/code&gt; — are plumbing rather than named patterns, so the honest count of distinct EIP is around thirty plus the 6 load-balancer strategies.)&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Message Routing&lt;/strong&gt; — &lt;code&gt;Choice&lt;/code&gt; (Content-Based Router), &lt;code&gt;Filter&lt;/code&gt;, &lt;code&gt;Splitter&lt;/code&gt; (+ streaming splitter), &lt;code&gt;Aggregator&lt;/code&gt;, &lt;code&gt;Resequencer&lt;/code&gt;, &lt;code&gt;Multicast&lt;/code&gt;, &lt;code&gt;RecipientList&lt;/code&gt;, &lt;code&gt;ScatterGather&lt;/code&gt;, &lt;code&gt;DynamicRouter&lt;/code&gt;, &lt;code&gt;LoadBalance&lt;/code&gt; (Round-Robin / Random / Weighted / Sticky / Failover).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Message Transformation&lt;/strong&gt; — &lt;code&gt;Marshal&lt;/code&gt;/&lt;code&gt;Unmarshal&lt;/code&gt; and the data-format registry, &lt;code&gt;ConvertBody&lt;/code&gt;, &lt;code&gt;Enrich&lt;/code&gt;/&lt;code&gt;PollEnrich&lt;/code&gt; (Content Enricher), &lt;code&gt;ClaimCheck&lt;/code&gt;, &lt;code&gt;StreamCaching&lt;/code&gt;, &lt;code&gt;Normalize&lt;/code&gt;, and &lt;code&gt;Transform&lt;/code&gt;/&lt;code&gt;SetBody&lt;/code&gt;/&lt;code&gt;SetHeader&lt;/code&gt; over the expression engine.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Messaging Endpoints&lt;/strong&gt; — &lt;code&gt;IdempotentConsumer&lt;/code&gt;, &lt;code&gt;WireTap&lt;/code&gt;, competing consumers (&lt;code&gt;seda&lt;/code&gt;/&lt;code&gt;vm&lt;/code&gt; &lt;code&gt;concurrentConsumers&lt;/code&gt;), &lt;code&gt;Pipeline&lt;/code&gt;, request/reply (&lt;code&gt;InOut&lt;/code&gt;), &lt;code&gt;Bean&amp;lt;T&amp;gt;&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;System Management &amp;amp; Reliability&lt;/strong&gt; — &lt;code&gt;OnException&lt;/code&gt; / &lt;code&gt;TryCatch&lt;/code&gt; / &lt;code&gt;DeadLetterChannel&lt;/code&gt;, &lt;code&gt;CircuitBreaker&lt;/code&gt;, &lt;code&gt;Throttle&lt;/code&gt; / &lt;code&gt;KeyedThrottle&lt;/code&gt;, &lt;code&gt;Delay&lt;/code&gt; / &lt;code&gt;Debounce&lt;/code&gt; / &lt;code&gt;Sampling&lt;/code&gt;, &lt;code&gt;Timeout&lt;/code&gt;, &lt;code&gt;Loop&lt;/code&gt;, &lt;code&gt;Saga&lt;/code&gt; (+ compensation), &lt;code&gt;RoutePolicy&lt;/code&gt;, &lt;code&gt;Transaction&lt;/code&gt;/&lt;code&gt;Transacted&lt;/code&gt;, and &lt;code&gt;Validate&lt;/code&gt; (JSON Schema / XSD).&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Track C — the expression &amp;amp; predicate engine (its own article, and it earns it)
&lt;/h3&gt;

&lt;p&gt;The one piece every other track quietly depends on. I almost wrote it off as string interpolation in the roadmap — then I read the source. It's a genuine &lt;strong&gt;compiled little language&lt;/strong&gt;: &lt;code&gt;Tokenizer&lt;/code&gt; → &lt;code&gt;Parser&lt;/code&gt; → an AST (&lt;code&gt;BinaryOperationNode&lt;/code&gt;, &lt;code&gt;UnaryOperationNode&lt;/code&gt;, &lt;code&gt;FunctionCallNode&lt;/code&gt;, &lt;code&gt;TernaryNode&lt;/code&gt;, &lt;code&gt;PostfixOperationNode&lt;/code&gt;, property/index access), lowered to &lt;code&gt;System.Linq.Expressions&lt;/code&gt; and &lt;code&gt;.Compile()&lt;/code&gt;d to real IL — not tree-walked at runtime. There's a dedicated article because there's a dedicated language:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The &lt;code&gt;${...}&lt;/code&gt; Simple-style template&lt;/strong&gt; isn't formatting — it's the entry point to the whole grammar: arithmetic (&lt;code&gt;+ - * /&lt;/code&gt;), comparison, &lt;code&gt;AND&lt;/code&gt;/&lt;code&gt;OR&lt;/code&gt;/&lt;code&gt;XOR&lt;/code&gt;/&lt;code&gt;NOT&lt;/code&gt;, the &lt;strong&gt;ternary &lt;code&gt;?:&lt;/code&gt;&lt;/strong&gt;, even &lt;strong&gt;postfix &lt;code&gt;++&lt;/code&gt;/&lt;code&gt;--&lt;/code&gt;&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;~22 built-in functions&lt;/strong&gt;, mined from the AST's &lt;code&gt;FunctionCallNode&lt;/code&gt;: string (&lt;code&gt;concat&lt;/code&gt;, &lt;code&gt;upper&lt;/code&gt;, &lt;code&gt;lower&lt;/code&gt;, &lt;code&gt;trim&lt;/code&gt;, &lt;code&gt;substring&lt;/code&gt;, &lt;code&gt;replace&lt;/code&gt;, &lt;code&gt;length&lt;/code&gt;, &lt;code&gt;contains&lt;/code&gt;, &lt;code&gt;startswith&lt;/code&gt;, &lt;code&gt;endswith&lt;/code&gt;), numeric (&lt;code&gt;abs&lt;/code&gt;, &lt;code&gt;round&lt;/code&gt;, &lt;code&gt;min&lt;/code&gt;, &lt;code&gt;max&lt;/code&gt;), &lt;strong&gt;aggregates&lt;/strong&gt; (&lt;code&gt;sum&lt;/code&gt;, &lt;code&gt;avg&lt;/code&gt;, &lt;code&gt;count&lt;/code&gt;), date math (&lt;code&gt;now&lt;/code&gt;, &lt;code&gt;dateformat&lt;/code&gt;, &lt;code&gt;dateadd&lt;/code&gt;), and the two that bridge into the structured world: &lt;code&gt;jpath(...)&lt;/code&gt; and &lt;code&gt;xpath(...)&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;JSONPath and XPath&lt;/strong&gt; as first-class expression types — typed (&lt;code&gt;TypedJsonPathExpression&lt;/code&gt;, &lt;code&gt;TypedXPathExpression&lt;/code&gt;) and &lt;strong&gt;precompiled&lt;/strong&gt; (&lt;code&gt;CompiledJPathExpression&lt;/code&gt;, &lt;code&gt;CompiledXPathExpression&lt;/code&gt;) when the path itself is static.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The predicate builders&lt;/strong&gt; — the fluent half, for &lt;code&gt;.Filter&lt;/code&gt;/&lt;code&gt;.Choice&lt;/code&gt;/&lt;code&gt;.Validate&lt;/code&gt;: &lt;code&gt;isEqualTo&lt;/code&gt;, &lt;code&gt;isNotEqualTo&lt;/code&gt;, &lt;code&gt;isGreaterThan&lt;/code&gt;/&lt;code&gt;isLessThan&lt;/code&gt; (&lt;code&gt;…OrEqualTo&lt;/code&gt;), &lt;code&gt;isBetween&lt;/code&gt;, &lt;code&gt;contains&lt;/code&gt;, &lt;code&gt;startsWith&lt;/code&gt;, &lt;code&gt;endsWith&lt;/code&gt;, &lt;code&gt;regex&lt;/code&gt;, &lt;code&gt;In(...)&lt;/code&gt;, &lt;code&gt;isNull&lt;/code&gt;/&lt;code&gt;isNotNull&lt;/code&gt;, composed with &lt;code&gt;and&lt;/code&gt;/&lt;code&gt;or&lt;/code&gt;/&lt;code&gt;not&lt;/code&gt;. Seventeen of them, each a real &lt;code&gt;IPredicate&lt;/code&gt;, not a closure.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Four separate &lt;code&gt;ConcurrentDictionary&lt;/code&gt; compile-and-cache pools&lt;/strong&gt; (template / property-resolver / logical / value) so every one of the above compiles &lt;strong&gt;once&lt;/strong&gt; per distinct expression string and runs as a cached delegate forever after.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That's why it's its own installment, and honestly why it might be two: the template grammar and the predicate DSL are different front-ends onto the same compiler.&lt;/p&gt;

&lt;h3&gt;
  
  
  Track D — the 22 connectors, one article each
&lt;/h3&gt;

&lt;p&gt;One focused article per connector, every example &lt;strong&gt;lifted from real production routes&lt;/strong&gt;, not toy snippets:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Brokers &amp;amp; messaging&lt;/strong&gt; — &lt;code&gt;kafka&lt;/code&gt;, &lt;code&gt;rabbitmq&lt;/code&gt;, &lt;code&gt;amqp&lt;/code&gt;, &lt;code&gt;asb&lt;/code&gt; (Azure Service Bus), &lt;code&gt;wmq&lt;/code&gt; (IBM MQ), &lt;code&gt;mqtt&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;RPC &amp;amp; web&lt;/strong&gt; — &lt;code&gt;http&lt;/code&gt;/&lt;code&gt;https&lt;/code&gt;, &lt;code&gt;grpc&lt;/code&gt;, &lt;code&gt;signalr&lt;/code&gt;, &lt;code&gt;ws&lt;/code&gt;/&lt;code&gt;wss&lt;/code&gt;, &lt;code&gt;tcp&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Data &amp;amp; storage&lt;/strong&gt; — &lt;code&gt;sql&lt;/code&gt;, &lt;code&gt;redis&lt;/code&gt;, &lt;code&gt;elasticsearch&lt;/code&gt;, &lt;code&gt;s3&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Files &amp;amp; transfer&lt;/strong&gt; — &lt;code&gt;file&lt;/code&gt;, &lt;code&gt;ftp&lt;/code&gt;, &lt;code&gt;sftp&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Enterprise &amp;amp; misc&lt;/strong&gt; — &lt;code&gt;smtp&lt;/code&gt;/&lt;code&gt;pop3&lt;/code&gt;/&lt;code&gt;imap&lt;/code&gt; (mail), &lt;code&gt;ldap&lt;/code&gt;, &lt;code&gt;fcm&lt;/code&gt;/&lt;code&gt;fstore&lt;/code&gt;/&lt;code&gt;fbstorage&lt;/code&gt; (Firebase), &lt;code&gt;qtimer&lt;/code&gt;/&lt;code&gt;cron&lt;/code&gt; (Quartz).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We start with Track A on purpose. Every pattern in Track B, every expression in Track C, and every connector in Track D is built on two things: a &lt;strong&gt;channel&lt;/strong&gt; that carries a message from one route segment to the next, and the &lt;strong&gt;&lt;code&gt;Exchange&lt;/code&gt;&lt;/strong&gt; that &lt;em&gt;is&lt;/em&gt; the message. Get these two right and the rest of the series is just composition. Get them wrong and you'll spend an afternoon wondering why your transaction silently didn't roll back.&lt;/p&gt;

&lt;p&gt;A pipeline in redb.Route is &lt;code&gt;From → [processors] → To&lt;/code&gt;. Let's look at what actually flows through it.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Exchange — the heart, and it isn't simple
&lt;/h2&gt;

&lt;p&gt;Everything that moves through a route is an &lt;code&gt;IExchange&lt;/code&gt;. Not a &lt;code&gt;byte[]&lt;/code&gt;, not your DTO — an &lt;code&gt;Exchange&lt;/code&gt;, a small object with more going on inside it than the name suggests. Here's the shape that matters:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;IExchange&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;IAsyncDisposable&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;IMessage&lt;/span&gt;  &lt;span class="n"&gt;In&lt;/span&gt;  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;     &lt;span class="c1"&gt;// the primary message — always present&lt;/span&gt;
    &lt;span class="n"&gt;IMessage&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;Out&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;     &lt;span class="c1"&gt;// the reply — lazy, null until you need it&lt;/span&gt;
    &lt;span class="n"&gt;ExchangePattern&lt;/span&gt; &lt;span class="n"&gt;Pattern&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;   &lt;span class="c1"&gt;// InOnly (default), InOut, or OutOnly&lt;/span&gt;

    &lt;span class="n"&gt;IDictionary&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;object&lt;/span&gt;&lt;span class="p"&gt;?&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Properties&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;   &lt;span class="c1"&gt;// route-level metadata&lt;/span&gt;
    &lt;span class="n"&gt;Exception&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;Exception&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;                 &lt;span class="c1"&gt;// error state, in-band&lt;/span&gt;
    &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="n"&gt;ExceptionHandled&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;ExchangeId&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;                         &lt;span class="c1"&gt;// identity, stable across clones&lt;/span&gt;

    &lt;span class="n"&gt;IExchange&lt;/span&gt; &lt;span class="nf"&gt;Clone&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;                                 &lt;span class="c1"&gt;// deep-ish copy + NEW DI scope&lt;/span&gt;
    &lt;span class="n"&gt;IServiceProvider&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;ServiceProvider&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;         &lt;span class="c1"&gt;// per-exchange DI scope&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And the message it carries:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;IMessage&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;object&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;Body&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;                        &lt;span class="c1"&gt;// your payload — any object, or null&lt;/span&gt;
    &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;ContentType&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;IDictionary&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;object&lt;/span&gt;&lt;span class="p"&gt;?&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Headers&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;     &lt;span class="c1"&gt;// metadata that DOES travel to brokers&lt;/span&gt;
    &lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;GetHeader&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;IMessage&lt;/span&gt; &lt;span class="nf"&gt;Clone&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Five things about this object decide how every channel and every pattern behaves. None of them are obvious from the type signature.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. &lt;code&gt;In&lt;/code&gt; vs &lt;code&gt;Out&lt;/code&gt;, and the &lt;code&gt;Pattern&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;In&lt;/code&gt; is the message coming through. &lt;code&gt;Out&lt;/code&gt; is the reply, and it's &lt;strong&gt;lazy&lt;/strong&gt; — for the default &lt;code&gt;InOnly&lt;/code&gt; pattern it stays &lt;code&gt;null&lt;/code&gt; and is never allocated. It only appears when a processor explicitly sets it (request/reply, or &lt;code&gt;.Respond()&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;There are &lt;strong&gt;three&lt;/strong&gt; patterns, not two — this is the Apache Camel 2.x model, ported wholesale:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;enum&lt;/span&gt; &lt;span class="n"&gt;ExchangePattern&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;InOnly&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;   &lt;span class="c1"&gt;// fire-and-forget. Producer result is written to In; Out stays null. (default)&lt;/span&gt;
    &lt;span class="n"&gt;InOut&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;   &lt;span class="c1"&gt;// request/reply. Original preserved in In, response written to Out.&lt;/span&gt;
    &lt;span class="n"&gt;OutOnly&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;   &lt;span class="c1"&gt;// explicit response via .Respond(); the RPC reply is taken from Out.&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here's the part the type signature hides, and the one place people get it wrong: &lt;strong&gt;&lt;code&gt;HasOut&lt;/code&gt; does &lt;em&gt;not&lt;/em&gt; tell you where the answer is.&lt;/strong&gt; Even on an &lt;code&gt;InOut&lt;/code&gt; exchange, a processor isn't obligated to populate &lt;code&gt;Out&lt;/code&gt; — if it just mutates &lt;code&gt;In.Body&lt;/code&gt;, the result lives in &lt;code&gt;In&lt;/code&gt;. So the framework never trusts &lt;code&gt;HasOut&lt;/code&gt; to find a reply. It reads &lt;strong&gt;&lt;code&gt;Out ?? In&lt;/code&gt;&lt;/strong&gt;, every time:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// ProducerTemplate.RequestBody — the canonical reply-extraction rule&lt;/span&gt;
&lt;span class="n"&gt;exchange&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Pattern&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ExchangePattern&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;InOut&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;producer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;exchange&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;exchange&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Out&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="n"&gt;Body&lt;/span&gt; &lt;span class="p"&gt;??&lt;/span&gt; &lt;span class="n"&gt;exchange&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;In&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Body&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;   &lt;span class="c1"&gt;// Out if present, otherwise In&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is verbatim how Camel's &lt;code&gt;ProducerTemplate&lt;/code&gt; extracts a result (&lt;code&gt;getResultMessage&lt;/code&gt;: "has Out → use Out, else In"). Copy that rule into your own code — &lt;code&gt;exchange.Out ?? exchange.In&lt;/code&gt; — and you'll never chase a reply that quietly stayed in &lt;code&gt;In&lt;/code&gt;. &lt;code&gt;HasOut&lt;/code&gt; is a fact about &lt;em&gt;allocation&lt;/em&gt;, not about &lt;em&gt;where the data is&lt;/em&gt;; don't use it to route on the answer.&lt;/p&gt;

&lt;p&gt;One honesty note for the JVM crowd: the live &lt;code&gt;Out&lt;/code&gt; message and the &lt;code&gt;OutOnly&lt;/code&gt; pattern are &lt;strong&gt;Camel 2.x semantics.&lt;/strong&gt; Camel 3+ deprecated &lt;code&gt;getOut()&lt;/code&gt;/&lt;code&gt;setOut()&lt;/code&gt; and collapsed the pattern set toward &lt;code&gt;InOnly&lt;/code&gt;/&lt;code&gt;InOut&lt;/code&gt;, precisely because a separate Out message copied headers and bred subtle bugs. redb.Route keeps the fuller 2.x model on purpose — but if you're coming from modern Camel, that's the difference you'll notice first.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. &lt;code&gt;Properties&lt;/code&gt; vs &lt;code&gt;Headers&lt;/code&gt; — the distinction that leaks bugs
&lt;/h3&gt;

&lt;p&gt;Both are &lt;code&gt;IDictionary&amp;lt;string, object?&amp;gt;&lt;/code&gt;. They are &lt;em&gt;not&lt;/em&gt; interchangeable:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;In.Headers&lt;/code&gt;&lt;/strong&gt; travel &lt;strong&gt;with the message to the broker.&lt;/strong&gt; Put a &lt;code&gt;correlationId&lt;/code&gt; here and Kafka/RabbitMQ carry it downstream.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;exchange.Properties&lt;/code&gt;&lt;/strong&gt; are &lt;strong&gt;route-level metadata&lt;/strong&gt; — &lt;code&gt;RouteId&lt;/code&gt;, transaction markers, your own scratch state. They &lt;strong&gt;do not&lt;/strong&gt; leave the process (the interface XML doc says exactly that: &lt;em&gt;"Does NOT travel to brokers — use In.Headers for that"&lt;/em&gt;). Stash a &lt;code&gt;DbContext&lt;/code&gt; handle or a retry counter here.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Put a value in the wrong dictionary and it either fails to reach the consumer (you used Properties) or leaks internal state onto the wire (you used Headers). The compiler won't catch it; both are just string-keyed dictionaries. Knowing which is which is half of using the framework correctly.&lt;/p&gt;

&lt;p&gt;Read either with the typed accessors instead of casting by hand:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;attempt&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;exchange&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetProperty&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="s"&gt;"retryCount"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;     &lt;span class="c1"&gt;// route-level, stays in-process&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;corr&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;exchange&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;In&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetHeader&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="s"&gt;"correlationId"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// travels to the broker&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  The well-known keys the framework itself writes
&lt;/h4&gt;

&lt;p&gt;This is the part that makes the section concrete, and it's the answer to "what's actually &lt;em&gt;in&lt;/em&gt; Properties?" The pipeline and the processors populate a set of reserved keys as your exchange flows. There is no single &lt;code&gt;ExchangeProperties&lt;/code&gt; constants file — they live next to the processor that owns each one — but here is the real registry, mined from source.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;exchange.Properties&lt;/code&gt; — route-level, never leave the process:&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Key&lt;/th&gt;
&lt;th&gt;Constant in code&lt;/th&gt;
&lt;th&gt;Written by&lt;/th&gt;
&lt;th&gt;Meaning&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;TRANSACT_ACTION&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;TransactedProcessor.TransactActionPropertyKey&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;.Transacted()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;the transacted-action stack for synchronization&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;TRANSACTION_SCOPE&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;BeginTransactionProcessor.ScopePropertyKey&lt;/code&gt; (internal)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;.Transaction()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;the live &lt;code&gt;TransactionScope&lt;/code&gt; for the block&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;CamelDuplicateMessage&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;IdempotentConsumerProcessor.DuplicatePropertyKey&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Idempotent Consumer&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;true&lt;/code&gt; when the message was seen before&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ClaimCheck.Stack&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;ClaimCheckHeaders.StackPropertyKey&lt;/code&gt; (internal)&lt;/td&gt;
&lt;td&gt;Claim Check&lt;/td&gt;
&lt;td&gt;the stack of stored payload keys&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;ValidationErrors&lt;/code&gt; / &lt;code&gt;ValidationResult&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ValidateProcessor.*Property&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;.Validate()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;validation outcome for the current exchange&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;CamelSplitSize&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;— (streaming splitter)&lt;/td&gt;
&lt;td&gt;streaming Splitter&lt;/td&gt;
&lt;td&gt;running count of split parts&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;__redb_scope:*&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;— (prefix)&lt;/td&gt;
&lt;td&gt;DI plumbing&lt;/td&gt;
&lt;td&gt;named child DI scopes, freed by &lt;code&gt;ReleaseScopes()&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Plus &lt;code&gt;RouteId&lt;/code&gt; is promoted to a &lt;strong&gt;first-class property&lt;/strong&gt; on the exchange (&lt;code&gt;exchange.RouteId&lt;/code&gt;), not just a dictionary entry — that's what the logger prints as &lt;code&gt;[rId:…]&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;In.Headers&lt;/code&gt; — travel with the message:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The Camel-compatible message headers come from the same world Camel users expect. The Splitter, for example, stamps every part:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// SplitterProcessor — each split message carries its coordinates&lt;/span&gt;
&lt;span class="n"&gt;splitMessage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Headers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"CamelSplitIndex"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;          &lt;span class="c1"&gt;// 0-based position&lt;/span&gt;
&lt;span class="n"&gt;splitMessage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Headers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"CamelSplitSize"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;total&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;          &lt;span class="c1"&gt;// batch size&lt;/span&gt;
&lt;span class="n"&gt;splitMessage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Headers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"CamelSplitComplete"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// last one?&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and every transport contributes its own namespaced header constants — &lt;code&gt;KafkaHeaders&lt;/code&gt; (&lt;code&gt;redbKafka.Topic&lt;/code&gt;, &lt;code&gt;redbKafka.Partition&lt;/code&gt;, &lt;code&gt;redbKafka.Offset&lt;/code&gt;, …), &lt;code&gt;SqlHeaders&lt;/code&gt; (&lt;code&gt;redbSql.rowCount&lt;/code&gt;, &lt;code&gt;redbSql.generatedKeys&lt;/code&gt;, …), &lt;code&gt;SignalRHeaders&lt;/code&gt; (&lt;code&gt;redbSignalR.ConnectionId&lt;/code&gt;, …), &lt;code&gt;TcpHeaders&lt;/code&gt;, &lt;code&gt;WsHeaders&lt;/code&gt;, &lt;code&gt;ElasticsearchHeaders&lt;/code&gt;. Each is a &lt;code&gt;static class&lt;/code&gt; of &lt;code&gt;public const string&lt;/code&gt; so you bind against &lt;code&gt;KafkaHeaders.Offset&lt;/code&gt;, not a stringly-typed &lt;code&gt;"redbKafka.Offset"&lt;/code&gt; you might misspell. The rule of thumb: anything prefixed &lt;code&gt;Camel*&lt;/code&gt; or &lt;code&gt;redb&amp;lt;Transport&amp;gt;.*&lt;/code&gt; is a header (on the wire); anything in &lt;code&gt;Properties&lt;/code&gt; is yours and the process's alone.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. The exception travels &lt;em&gt;in-band&lt;/em&gt;
&lt;/h3&gt;

&lt;p&gt;When a processor throws, the exception doesn't just unwind the stack — it's captured onto &lt;code&gt;exchange.Exception&lt;/code&gt;, with an &lt;code&gt;ExceptionHandled&lt;/code&gt; flag beside it. That's what makes a dead-letter route able to branch on &lt;em&gt;why&lt;/em&gt; something failed (&lt;code&gt;when e.Exception is TimeoutException → …&lt;/code&gt;) instead of just &lt;em&gt;that&lt;/em&gt; it failed. The error becomes data you can route on. We lean on this hard in the error-handling installment.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. &lt;code&gt;ExchangeId&lt;/code&gt; is stable across clones
&lt;/h3&gt;

&lt;p&gt;Each exchange gets a &lt;code&gt;Guid&lt;/code&gt;-based &lt;code&gt;ExchangeId&lt;/code&gt; at creation. The non-obvious part: &lt;strong&gt;&lt;code&gt;Clone()&lt;/code&gt; preserves it.&lt;/strong&gt; A split into 500 parts, or a &lt;code&gt;seda&lt;/code&gt; hop that clones the exchange, keeps the same id — so your logs and traces stitch the whole flow back to one origin. Identity survives copying; that's deliberate.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. The DI scope — and four ways to copy an exchange
&lt;/h3&gt;

&lt;p&gt;This is the part that's genuinely not simple, and it's the reason the channels behave the way they do.&lt;/p&gt;

&lt;p&gt;An &lt;code&gt;Exchange&lt;/code&gt; can own a &lt;strong&gt;DI scope&lt;/strong&gt; — a per-message &lt;code&gt;IServiceScope&lt;/code&gt;. Processors resolve scoped services (&lt;code&gt;DbContext&lt;/code&gt;, &lt;code&gt;IRedbService&lt;/code&gt;, …) from &lt;code&gt;exchange.ServiceProvider&lt;/code&gt;, and they get the &lt;em&gt;same&lt;/em&gt; instances for the lifetime of that exchange. A &lt;code&gt;TransactionScope&lt;/code&gt; lives in exactly that scope. So the question "are these two exchanges in the same transaction?" reduces to "do they share a DI scope?"&lt;/p&gt;

&lt;p&gt;There are &lt;strong&gt;four&lt;/strong&gt; ways an exchange gets copied, and they differ &lt;em&gt;only&lt;/em&gt; in what they do with that scope:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Method&lt;/th&gt;
&lt;th&gt;Body/Headers&lt;/th&gt;
&lt;th&gt;DI scope&lt;/th&gt;
&lt;th&gt;Owns scope?&lt;/th&gt;
&lt;th&gt;Use&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Clone()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;copied&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;new scope&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;hand-off to another thread (&lt;code&gt;seda&lt;/code&gt;, &lt;code&gt;vm&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;CloneLinked()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;copied&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;shares parent's&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;td&gt;parallel fan-out inside the parent's transaction&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;CreateChild(msg)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;new message&lt;/td&gt;
&lt;td&gt;new scope&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;a derived exchange, independent lifetime&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;CreateLinkedChild(msg)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;new message&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;shares parent's&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;td&gt;sequential children reusing the same connection&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// from Exchange.Clone() — the scope-creating branch&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_scopeFactory&lt;/span&gt; &lt;span class="p"&gt;!=&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;clone&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_scopeFactory&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_scopeFactory&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;clone&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_scope&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_scopeFactory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CreateScope&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;   &lt;span class="c1"&gt;// &amp;lt;-- a brand-new scope&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// from Exchange.CloneLinked() — the scope-sharing branch&lt;/span&gt;
&lt;span class="n"&gt;_ownsScope&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="n"&gt;_scope&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_scope&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;            &lt;span class="c1"&gt;// &amp;lt;-- the SAME scope, and we won't dispose it&lt;/span&gt;
&lt;span class="n"&gt;_scopeFactory&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_scopeFactory&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Hold onto that table. The entire transaction story of &lt;code&gt;seda&lt;/code&gt; vs &lt;code&gt;direct&lt;/code&gt;, and of Multicast vs a broker hop, is just &lt;em&gt;which row got used&lt;/em&gt;. &lt;code&gt;Clone()&lt;/code&gt; (new scope) means a new transaction; &lt;code&gt;CloneLinked()&lt;/code&gt; (shared scope) means the same one.&lt;/p&gt;

&lt;p&gt;There's also &lt;code&gt;ReleaseScopes()&lt;/code&gt; — it disposes the DI scopes &lt;strong&gt;without touching the &lt;code&gt;Body&lt;/code&gt;&lt;/strong&gt;, so an aggregator can free database connections early while still holding the message data it's accumulating. And &lt;code&gt;DisposeAsync()&lt;/code&gt; cleans up both the body (streams, stream caches) and the scopes. The object is &lt;code&gt;IAsyncDisposable&lt;/code&gt; for a reason.&lt;/p&gt;

&lt;h3&gt;
  
  
  The gotcha: &lt;code&gt;Clone()&lt;/code&gt; does NOT deep-copy the Body
&lt;/h3&gt;

&lt;p&gt;Read &lt;code&gt;Message.Clone()&lt;/code&gt; literally:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;IMessage&lt;/span&gt; &lt;span class="nf"&gt;Clone&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;clone&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;Message&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Body&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;ContentType&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ContentType&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;  &lt;span class="c1"&gt;// Body reference copied&lt;/span&gt;
    &lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;kvp&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;_headers&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;clone&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_headers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;kvp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Key&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;kvp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;clone&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Headers get a fresh dictionary. &lt;strong&gt;&lt;code&gt;Body&lt;/code&gt; is copied by reference.&lt;/strong&gt; After a &lt;code&gt;seda&lt;/code&gt; hop the producer's exchange and the worker's clone have independent headers, properties, and DI scopes — but they point at the &lt;strong&gt;same body object.&lt;/strong&gt; If that body is a mutable &lt;code&gt;List&amp;lt;T&amp;gt;&lt;/code&gt; or a POCO and both sides write to it, you have a data race the cloning &lt;em&gt;looks&lt;/em&gt; like it prevented. The XML doc says "deep copy"; the honest truth is "deep copy of everything except the payload." Treat the body as immutable once it's in flight, or clone it yourself.&lt;/p&gt;

&lt;h3&gt;
  
  
  Two APIs on purpose
&lt;/h3&gt;

&lt;p&gt;One last thing you'll notice in IntelliSense: every member has a C# idiomatic form (&lt;code&gt;In&lt;/code&gt;, &lt;code&gt;Body&lt;/code&gt;, &lt;code&gt;GetHeader&amp;lt;T&amp;gt;&lt;/code&gt;) and a Java-style alias (&lt;code&gt;getIn()&lt;/code&gt;, &lt;code&gt;setBody()&lt;/code&gt;, &lt;code&gt;getHeader&amp;lt;T&amp;gt;()&lt;/code&gt;). They're default interface methods over the same state, kept so the model reads the same as Apache Camel for anyone coming from the JVM. Use whichever; they're the same object.&lt;/p&gt;

&lt;p&gt;Now — the wires that carry this thing.&lt;/p&gt;




&lt;h2&gt;
  
  
  The four channels — two axes
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;direct&lt;/code&gt;, &lt;code&gt;direct-vm&lt;/code&gt;, &lt;code&gt;seda&lt;/code&gt;, &lt;code&gt;vm&lt;/code&gt; are how route segments talk to each other &lt;em&gt;inside&lt;/em&gt; a process. Choosing between them is the single most common thing newcomers get wrong, and now you have the vocabulary for &lt;em&gt;why&lt;/em&gt;: it comes down to threading and scope. They split on two axes — &lt;strong&gt;sync vs async&lt;/strong&gt;, and &lt;strong&gt;one context vs across contexts&lt;/strong&gt;:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Scheme&lt;/th&gt;
&lt;th&gt;Sync/Async&lt;/th&gt;
&lt;th&gt;Scope&lt;/th&gt;
&lt;th&gt;Clones the exchange?&lt;/th&gt;
&lt;th&gt;Same transaction?&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;direct://&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;synchronous&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;one context&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;yes&lt;/strong&gt; — same thread, same scope&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;direct-vm://&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;synchronous&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;across contexts&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;seda://&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;asynchronous&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;one context&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;yes&lt;/strong&gt; (&lt;code&gt;Clone()&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;no&lt;/strong&gt; — new scope&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;vm://&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;asynchronous&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;across contexts&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;direct&lt;/code&gt; is the contrast. &lt;code&gt;seda&lt;/code&gt; is where the real work — and the real footguns — live, so that's where we'll spend the page.&lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;code&gt;direct://&lt;/code&gt; — a method call wearing a URI
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;direct&lt;/code&gt; is not a queue. No thread, no buffer. A producer sending to a &lt;code&gt;direct&lt;/code&gt; endpoint invokes the consumer's processor &lt;strong&gt;synchronously, on the same thread&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// the whole of DirectProducer.Process&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;processor&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_endpoint&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ConsumerProcessor&lt;/span&gt;
    &lt;span class="p"&gt;??&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;InvalidOperationException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"No consumer registered for direct endpoint ..."&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;processor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;exchange&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The exchange is &lt;strong&gt;not cloned&lt;/strong&gt;. Same object, same thread, same DI scope — straight to the consumer. Three consequences follow:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Exceptions propagate back to the caller.&lt;/strong&gt; A throw in the &lt;code&gt;direct&lt;/code&gt; consumer surfaces in the producer's route, where &lt;code&gt;OnException&lt;/code&gt;/&lt;code&gt;DoTry&lt;/code&gt; can catch it. (Remember §3 — it also lands on &lt;code&gt;exchange.Exception&lt;/code&gt;.)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It's the same transaction.&lt;/strong&gt; Same scope from the table above, so a &lt;code&gt;direct&lt;/code&gt; hop inside a &lt;code&gt;.Transaction()&lt;/code&gt; block commits and rolls back with everything around it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The consumer must be started first&lt;/strong&gt;, or the send throws. &lt;code&gt;direct&lt;/code&gt; decouples your &lt;em&gt;route definitions&lt;/em&gt; into named sub-routes — not your &lt;em&gt;threads&lt;/em&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That's the entire personality of &lt;code&gt;direct&lt;/code&gt;: a zero-cost, in-transaction call you can give a URI and reuse. It has no parameters because it has no machinery. Use it to break a big route into readable, reusable pieces.&lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;code&gt;seda://&lt;/code&gt; — the async queue, in detail
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;seda&lt;/code&gt; (Staged Event-Driven Architecture) is the opposite of &lt;code&gt;direct&lt;/code&gt; in every cell of the table. It's a real in-memory queue built on &lt;code&gt;System.Threading.Channels&lt;/code&gt;. The producer &lt;strong&gt;enqueues and returns immediately&lt;/strong&gt;; one or more background workers drain the queue on their own threads.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// SedaProducer.Process — the whole thing&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;copy&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;exchange&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Clone&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;                       &lt;span class="c1"&gt;// §5: new scope. the trap lives here.&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;_endpoint&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Queue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Writer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;copy&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two facts are baked into those two lines, and everything else about &lt;code&gt;seda&lt;/code&gt; follows from them: it &lt;strong&gt;clones&lt;/strong&gt; (so the worker and producer never share scope — that &lt;code&gt;Clone()&lt;/code&gt; is row 1 of the table, a &lt;em&gt;new&lt;/em&gt; scope), and it &lt;strong&gt;returns before the work is done&lt;/strong&gt; (so the producer's thread, and its transaction, move on).&lt;/p&gt;

&lt;h3&gt;
  
  
  The parameters
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;seda&lt;/code&gt; takes three, all on the URI: &lt;code&gt;seda://name?concurrentConsumers=4&amp;amp;size=1000&amp;amp;timeout=30000&lt;/code&gt;.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Parameter&lt;/th&gt;
&lt;th&gt;Default&lt;/th&gt;
&lt;th&gt;What it does&lt;/th&gt;
&lt;th&gt;When to change it&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;concurrentConsumers&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;1&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Number of worker loops draining the queue in parallel&lt;/td&gt;
&lt;td&gt;Raise when the downstream is slower than the inflow and order doesn't matter&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;size&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;0&lt;/code&gt; (unbounded)&lt;/td&gt;
&lt;td&gt;Max queued exchanges. &lt;code&gt;0&lt;/code&gt; = grow without limit; &lt;code&gt;&amp;gt;0&lt;/code&gt; = bounded with &lt;code&gt;Wait&lt;/code&gt; backpressure&lt;/td&gt;
&lt;td&gt;Set a bound whenever the producer can outpace the consumer (almost always, in production)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;timeout&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;30000&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Declared as the enqueue wait for a bounded queue — &lt;strong&gt;see the honesty note below&lt;/strong&gt;
&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;concurrentConsumers&lt;/code&gt; — throughput, at the cost of order
&lt;/h3&gt;

&lt;p&gt;One worker is the default and keeps strict FIFO. Raising it spins up N independent loops:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// SedaConsumer.RunAsync&lt;/span&gt;
&lt;span class="n"&gt;_workers&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;_options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ConcurrentConsumers&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;_options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ConcurrentConsumers&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;++)&lt;/span&gt;
    &lt;span class="n"&gt;_workers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;WorkerLoop&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pollCt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;processingCt&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// each worker&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;exchange&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;_endpoint&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Queue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Reader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ReadAllAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pollCt&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;ProcessWithTracking&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;exchange&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;processingCt&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;Interlocked&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Increment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;ref&lt;/span&gt; &lt;span class="n"&gt;_processedCount&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two consequences worth stating plainly:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;You trade ordering for throughput.&lt;/strong&gt; With &lt;code&gt;concurrentConsumers=1&lt;/code&gt; the channel runs in &lt;code&gt;SingleReader&lt;/code&gt; mode (a real optimization in &lt;code&gt;System.Threading.Channels&lt;/code&gt;) and messages come out in order. With N&amp;gt;1, N workers pull concurrently and &lt;strong&gt;strict FIFO is gone&lt;/strong&gt; — message 2 can finish before message 1. Only raise it when out-of-order processing is acceptable.&lt;/li&gt;
&lt;li&gt;It's per-endpoint backpressure relief: a slow downstream stops blocking the upstream producer, because the producer only ever writes to the queue and leaves.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;size&lt;/code&gt; — bounded vs unbounded, and why you almost always want bounded
&lt;/h3&gt;

&lt;p&gt;This is the parameter people skip and regret. The endpoint picks the channel implementation off &lt;code&gt;size&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;Queue&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Size&lt;/span&gt; &lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;
    &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;Channel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CreateBounded&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IExchange&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;BoundedChannelOptions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Size&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="n"&gt;FullMode&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;BoundedChannelFullMode&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Wait&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;    &lt;span class="c1"&gt;// producer awaits a free slot&lt;/span&gt;
          &lt;span class="n"&gt;SingleReader&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ConcurrentConsumers&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="n"&gt;SingleWriter&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;
      &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Channel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CreateUnbounded&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IExchange&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="cm"&gt;/* ... */&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// grows until you run out of memory&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;size=0&lt;/code&gt; (default, unbounded):&lt;/strong&gt; the queue grows as fast as producers write. If the consumer can't keep up, that's an unbounded memory leak with extra steps. Fine for bursty, bounded-volume work; dangerous for a firehose.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;size&amp;gt;0&lt;/code&gt; (bounded):&lt;/strong&gt; &lt;code&gt;FullMode = Wait&lt;/code&gt; means a full queue makes the &lt;em&gt;producer await a free slot&lt;/em&gt; — backpressure that pushes the slowdown upstream instead of into your heap. This is what you want in production.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// bounded SEDA: 4 workers, at most 1000 queued, producer waits when full&lt;/span&gt;
&lt;span class="nf"&gt;From&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"seda://enrich?concurrentConsumers=4&amp;amp;size=1000"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;Enrich&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;To&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"rabbitmq://enriched"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  &lt;code&gt;timeout&lt;/code&gt; — an honesty note
&lt;/h3&gt;

&lt;p&gt;The options object documents &lt;code&gt;timeout&lt;/code&gt; (default &lt;code&gt;30000&lt;/code&gt; ms) as the enqueue wait for a bounded queue. Being straight with you: in the current code the producer enqueues with &lt;code&gt;WriteAsync(copy, ct)&lt;/code&gt; and &lt;strong&gt;does not&lt;/strong&gt; apply that timeout — a full bounded queue makes the producer wait on the channel until a slot frees or the &lt;code&gt;CancellationToken&lt;/code&gt; fires, not until 30 seconds elapse. So today, plan around &lt;code&gt;size&lt;/code&gt; and the cancellation token; treat &lt;code&gt;timeout&lt;/code&gt; as declared-but-not-yet-wired and don't build a deadline assumption on it. (Flagging it here because guessing at framework behavior from a doc-comment is exactly how you ship a bug.)&lt;/p&gt;

&lt;h3&gt;
  
  
  Graceful shutdown — &lt;code&gt;seda&lt;/code&gt; drains, it doesn't drop
&lt;/h3&gt;

&lt;p&gt;When a route stops, &lt;code&gt;seda&lt;/code&gt; doesn't throw away what's already queued:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// SedaConsumer.OnStopAccepting&lt;/span&gt;
&lt;span class="n"&gt;_endpoint&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Queue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Writer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;TryComplete&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;   &lt;span class="c1"&gt;// stop accepting new; let readers finish&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Completing the writer makes the workers' &lt;code&gt;ReadAllAsync&lt;/code&gt; loop finish the &lt;strong&gt;remaining&lt;/strong&gt; items and then exit cleanly (&lt;code&gt;SedaConsumer&lt;/code&gt; is a &lt;code&gt;DrainableConsumer&lt;/code&gt;). On a graceful stop, in-flight queued exchanges are processed, not lost.&lt;/p&gt;

&lt;h3&gt;
  
  
  The durability caveat
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;seda&lt;/code&gt; is &lt;strong&gt;in-memory and non-durable.&lt;/strong&gt; A graceful stop drains; a crash or a hard kill does &lt;strong&gt;not&lt;/strong&gt; — whatever was sitting in the channel is gone. &lt;code&gt;seda&lt;/code&gt; is at-most-once across a process restart. When you need the queue to survive a restart, that's a broker (&lt;code&gt;rabbitmq&lt;/code&gt;, &lt;code&gt;kafka&lt;/code&gt;), not &lt;code&gt;seda&lt;/code&gt;. &lt;code&gt;seda&lt;/code&gt; is for decoupling &lt;em&gt;within&lt;/em&gt; a process, not for durability.&lt;/p&gt;

&lt;h3&gt;
  
  
  The transaction trap, stated once and for all
&lt;/h3&gt;

&lt;p&gt;Now §5 pays off. Because &lt;code&gt;seda&lt;/code&gt; calls &lt;code&gt;Clone()&lt;/code&gt; — row 1, a &lt;strong&gt;new&lt;/strong&gt; DI scope on a &lt;strong&gt;different&lt;/strong&gt; thread — &lt;strong&gt;anything past a &lt;code&gt;seda://&lt;/code&gt; hop is not in the caller's transaction.&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="nf"&gt;From&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"kafka://orders"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Transaction&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;To&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Sql&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"INSERT …"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;Transacted&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;To&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"seda://post-process"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;     &lt;span class="c1"&gt;// runs in a NEW scope, on another thread,&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;EndTransaction&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;                 &lt;span class="c1"&gt;//    OUTSIDE this transaction&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If &lt;code&gt;post-process&lt;/code&gt; throws, the &lt;code&gt;INSERT&lt;/code&gt; above has &lt;strong&gt;already committed&lt;/strong&gt; — the &lt;code&gt;seda&lt;/code&gt; hop left the transaction the moment it cloned. This is the one mistake everyone makes exactly once. The fix is the table: if the hop must share the transaction, use &lt;code&gt;direct&lt;/code&gt; (no clone, same scope); if you genuinely want to hand the work off and move on, &lt;code&gt;seda&lt;/code&gt; is correct and you accept the new boundary. The DSL is the same; the scope is everything.&lt;/p&gt;

&lt;p&gt;Mental model: &lt;strong&gt;&lt;code&gt;direct&lt;/code&gt; = function call, &lt;code&gt;seda&lt;/code&gt; = mailbox.&lt;/strong&gt; One preserves your thread and your transaction; the other trades both for throughput and isolation.&lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;code&gt;direct-vm://&lt;/code&gt; and &lt;code&gt;vm://&lt;/code&gt; — the same two, across module boundaries
&lt;/h2&gt;

&lt;p&gt;In a multi-module host (this is how redb.Tsak runs several modules in one process) each module is its own &lt;code&gt;RouteContext&lt;/code&gt;. Plain &lt;code&gt;direct&lt;/code&gt; and &lt;code&gt;seda&lt;/code&gt; are scoped to a single context — a producer in module A can't see a &lt;code&gt;direct&lt;/code&gt; consumer in module B. The &lt;code&gt;-vm&lt;/code&gt; variants lift exactly that wall by sharing the processor registry, and for &lt;code&gt;vm&lt;/code&gt; the channel, through a &lt;code&gt;SharedVmRegistry&lt;/code&gt; DI singleton:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;direct-vm://&lt;/code&gt;&lt;/strong&gt; — synchronous, &lt;strong&gt;cross-context&lt;/strong&gt;, no clone. A consumer in the &lt;code&gt;billing&lt;/code&gt; module exposes &lt;code&gt;direct-vm://charge&lt;/code&gt;; a producer in &lt;code&gt;orders&lt;/code&gt; calls it like a local in-transaction method.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;vm://&lt;/code&gt;&lt;/strong&gt; — asynchronous, cross-context, cloned-and-queued. The cross-module twin of &lt;code&gt;seda&lt;/code&gt;, with the &lt;strong&gt;same&lt;/strong&gt; &lt;code&gt;concurrentConsumers&lt;/code&gt; and &lt;code&gt;size&lt;/code&gt; parameters (and the same &lt;code&gt;Clone()&lt;/code&gt;, so the same transaction boundary and the same shared-Body caveat).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The rule transfers cleanly: &lt;code&gt;direct-vm&lt;/code&gt; for a synchronous cross-module call that shares the caller's transaction; &lt;code&gt;vm&lt;/code&gt; for hand-it-off-and-move-on across modules. Same semantics as their in-context twins — just a wider blast radius.&lt;/p&gt;




&lt;h2&gt;
  
  
  Picking a channel
&lt;/h2&gt;

&lt;p&gt;The whole decision in one table:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;You want…&lt;/th&gt;
&lt;th&gt;Channel&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;A reusable sub-route, same thread, &lt;strong&gt;inside my transaction&lt;/strong&gt;
&lt;/td&gt;
&lt;td&gt;&lt;code&gt;direct://&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The above, but the consumer lives in &lt;strong&gt;another module&lt;/strong&gt;
&lt;/td&gt;
&lt;td&gt;&lt;code&gt;direct-vm://&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;To &lt;strong&gt;hand work off&lt;/strong&gt; to a background worker and not wait&lt;/td&gt;
&lt;td&gt;&lt;code&gt;seda://&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The above, across &lt;strong&gt;module&lt;/strong&gt; boundaries&lt;/td&gt;
&lt;td&gt;&lt;code&gt;vm://&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Survival across a &lt;strong&gt;process restart&lt;/strong&gt;
&lt;/td&gt;
&lt;td&gt;not a channel — a broker (&lt;code&gt;rabbitmq&lt;/code&gt;, &lt;code&gt;kafka&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;And the two facts that drive every row: &lt;strong&gt;does it clone (new scope = new transaction), and does it return before the work is done.&lt;/strong&gt; Everything else is detail.&lt;/p&gt;




&lt;h2&gt;
  
  
  What's next
&lt;/h2&gt;

&lt;p&gt;That's the foundation. You now know what flows through a route (&lt;code&gt;Exchange&lt;/code&gt; — In/Out, Properties vs Headers, in-band exceptions, and the four scope-aware clone variants) and the four wires that carry it (&lt;code&gt;direct&lt;/code&gt;/&lt;code&gt;direct-vm&lt;/code&gt; sync-and-in-transaction, &lt;code&gt;seda&lt;/code&gt;/&lt;code&gt;vm&lt;/code&gt; async-and-isolated), down to the parameter that decides whether your queue applies backpressure or eats your heap.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Next in the series — Part 2: Splitter + Aggregator.&lt;/strong&gt; We fan one message into many, process them with bounded parallelism, and re-assemble — and the &lt;code&gt;Clone()&lt;/code&gt; vs &lt;code&gt;CloneLinked()&lt;/code&gt; distinction from §5 turns out to be the whole story of whether the split shares the parent's transaction. Plus the aggregation-strategy contract where the first call hands you a &lt;code&gt;null&lt;/code&gt; accumulator. Subscribe to the series if you want it when it lands.&lt;/p&gt;

&lt;p&gt;If anything here fought you — especially the &lt;code&gt;seda&lt;/code&gt; transaction boundary or the shared-&lt;code&gt;Body&lt;/code&gt; clone — say so in the comments. That feedback is exactly what an early OSS release is for.&lt;/p&gt;




&lt;h2&gt;
  
  
  Links
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;redb.Route&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/redbase-app/redb-route" rel="noopener noreferrer"&gt;github.com/redbase-app/redb-route&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;redb.Tsak (runtime / multi-module host)&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/redbase-app/redb-tsak" rel="noopener noreferrer"&gt;github.com/redbase-app/redb-tsak&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;redb.Core (storage)&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/redbase-app/redb" rel="noopener noreferrer"&gt;github.com/redbase-app/redb&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Discussions&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/redbase-app/redb-route/discussions" rel="noopener noreferrer"&gt;github.com/redbase-app/redb-route/discussions&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;All Apache 2.0. Questions in the comments are welcome — especially "does channel X do Y?", because that's how the docs get written.&lt;/p&gt;

</description>
      <category>csharp</category>
      <category>dotnet</category>
      <category>opensource</category>
    </item>
    <item>
      <title>RedBase / redb.Route / redb.Tsak 3.0.0 shipped</title>
      <dc:creator>rinat kozin</dc:creator>
      <pubDate>Sun, 31 May 2026 14:26:56 +0000</pubDate>
      <link>https://dev.to/rinat_kozin_d0a2ef43e7824/redbase-redbroute-redbtsak-300-shipped-27pf</link>
      <guid>https://dev.to/rinat_kozin_d0a2ef43e7824/redbase-redbroute-redbtsak-300-shipped-27pf</guid>
      <description>&lt;p&gt;redb hit 20k NuGet downloads&lt;/p&gt;

&lt;p&gt;Three packages, one version bump.&lt;/p&gt;

&lt;p&gt;RedBase 3.0.0 — Free tier now generates the same pivot CTE as Pro. array_agg FILTER on PostgreSQL, MAX CASE WHEN on MSSql. Same base-field pushdown, same index path. MSSql Free went from 0 to 145/145 parity tests. Auto-deployed SQL bundle on version mismatch — no DBA in the loop.&lt;/p&gt;

&lt;p&gt;redb.Route 3.0.0 — the entire v1/v2 dual compiler stack is gone. One canonical RouteDefinition, AST built from IProcessorDefinition nodes. Dynamic endpoints (ToD, dynamic WireTap/Enrich), string-template DSL (${header.x}, ${body}), full OnException fluent parity with Apache Camel.&lt;/p&gt;

&lt;p&gt;redb.Tsak 3.0.0 — runtime container for redb.Route pipelines. Rebuilt on Core 3.0.0 + Route 3.0.0. No breaking changes in Tsak's own API.&lt;/p&gt;

&lt;p&gt;Upgrade: package bump, no migrations.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://dev.to/rinat_kozin_d0a2ef43e7824/why-our-free-was-so-far-behind-pro-and-what-we-just-shipped-in-redbase-300-with-the-actual-sql-i7b"&gt;https://dev.to/rinat_kozin_d0a2ef43e7824/why-our-free-was-so-far-behind-pro-and-what-we-just-shipped-in-redbase-300-with-the-actual-sql-i7b&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://redbase.app/architecture" rel="noopener noreferrer"&gt;https://redbase.app/architecture&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/redbase-app/redb/blob/main/CHANGELOG.md" rel="noopener noreferrer"&gt;https://github.com/redbase-app/redb/blob/main/CHANGELOG.md&lt;/a&gt;&lt;br&gt;
&lt;a href="https://github.com/redbase-app/redb-route/blob/main/CHANGELOG.md" rel="noopener noreferrer"&gt;https://github.com/redbase-app/redb-route/blob/main/CHANGELOG.md&lt;/a&gt;&lt;br&gt;
&lt;a href="https://github.com/redbase-app/redb-tsak/blob/main/CHANGELOG.md" rel="noopener noreferrer"&gt;https://github.com/redbase-app/redb-tsak/blob/main/CHANGELOG.md&lt;/a&gt;&lt;/p&gt;

</description>
      <category>status</category>
      <category>dotnet</category>
      <category>database</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Why our Free was so far behind Pro — and what we just shipped in RedBase 3.0.0 (with the actual SQL)</title>
      <dc:creator>rinat kozin</dc:creator>
      <pubDate>Fri, 29 May 2026 23:18:10 +0000</pubDate>
      <link>https://dev.to/rinat_kozin_d0a2ef43e7824/why-our-free-was-so-far-behind-pro-and-what-we-just-shipped-in-redbase-300-with-the-actual-sql-i7b</link>
      <guid>https://dev.to/rinat_kozin_d0a2ef43e7824/why-our-free-was-so-far-behind-pro-and-what-we-just-shipped-in-redbase-300-with-the-actual-sql-i7b</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fb39rp5la1lk52ev1d2wy.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fb39rp5la1lk52ev1d2wy.jpg" alt="RedBase (redB) logo rendered in fire-explosion style, with C# code snippets and data charts in the background" width="784" height="1168"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Since the launch posts I keep getting the same question, in DMs, issues, private chats:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"Why is the Free version so different from Pro? It feels like two different products."&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It's a fair question, and the honest answer isn't a marketing answer — it's a "this is how the codebase grew over the years" answer. So let me just tell you, and then show you the SQL — both tiers, with the actual parameter values, so you can see exactly what hits the database.&lt;/p&gt;

&lt;p&gt;This is one post. There won't be a second one. I'd rather over-explain once than dribble out a series.&lt;/p&gt;




&lt;h2&gt;
  
  
  30-second background
&lt;/h2&gt;

&lt;p&gt;RedBase is a typed object store. You write a C# class, decorate it with &lt;code&gt;[RedbScheme("name")]&lt;/code&gt;, the engine syncs the scheme into PostgreSQL or MSSql, and you query with normal LINQ:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;RedbScheme&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Employee"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;EmployeeProps&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;Name&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;Age&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;City&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;Address&lt;/span&gt; &lt;span class="n"&gt;HomeAddress&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;Dictionary&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Address&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;OfficeLocations&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;RedbListItem&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;Status&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;DateTimeOffset&lt;/span&gt; &lt;span class="n"&gt;DateCreate&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;employees&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;service&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Query&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;EmployeeProps&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Age&lt;/span&gt; &lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="m"&gt;30&lt;/span&gt; &lt;span class="p"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;City&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="s"&gt;"London"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;OrderBy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToListAsync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Storage is six tables: &lt;code&gt;_objects&lt;/code&gt;, &lt;code&gt;_values&lt;/code&gt;, &lt;code&gt;_structures&lt;/code&gt;, &lt;code&gt;_schemes&lt;/code&gt;, &lt;code&gt;_list_items&lt;/code&gt;, &lt;code&gt;_users&lt;/code&gt;. One row per object lives in &lt;code&gt;_objects&lt;/code&gt;. Each property of that object lives as one or more rows in &lt;code&gt;_values&lt;/code&gt;, keyed by &lt;code&gt;(_id_object, _id_structure, _array_index)&lt;/code&gt;. So filtering by N properties means joining/pivoting N rows back into one logical row per object.&lt;/p&gt;

&lt;p&gt;How you do that pivot defines the performance ceiling of the engine. That's the part 3.0.0 fixes for the Free tier.&lt;/p&gt;




&lt;h2&gt;
  
  
  How we got here — the two paths that existed before 3.0.0
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Pro path (since ~2024) — compiled in C
&lt;/h3&gt;

&lt;p&gt;Pro walks the LINQ expression tree in process via &lt;code&gt;ExpressionToSqlCompiler&lt;/code&gt; + &lt;code&gt;SqlExpressionVisitor&lt;/code&gt;, registers bind variables through &lt;code&gt;SqlParameterCollector&lt;/code&gt;, and emits a single PVT (pivot) CTE per query. For the LINQ above, with &lt;code&gt;_id_scheme=42&lt;/code&gt;, structure id &lt;code&gt;101=Age&lt;/code&gt;, &lt;code&gt;102=City&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Pro on PostgreSQL — exact text Npgsql sends to the server&lt;/span&gt;
&lt;span class="k"&gt;WITH&lt;/span&gt; &lt;span class="n"&gt;pvt_cte&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id_object&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;array_agg&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_Long&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;   &lt;span class="n"&gt;FILTER&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id_structure&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_array_index&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;))[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="nv"&gt;"Age"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;array_agg&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_String&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;FILTER&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id_structure&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_array_index&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;))[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="nv"&gt;"City"&lt;/span&gt;
  &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;_values&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;
  &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;_id&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;_objects&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;_id_scheme&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id_object&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id&lt;/span&gt;
  &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id_structure&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;ANY&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id_object&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;_objects&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;
&lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;pvt_cte&lt;/span&gt; &lt;span class="n"&gt;pvt&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pvt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id_object&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;pvt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"Age"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;pvt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"City"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;array_agg&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_String&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;FILTER&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="p"&gt;...))[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

&lt;span class="c1"&gt;-- bound parameters (NpgsqlParameter, never interpolated into text):&lt;/span&gt;
&lt;span class="c1"&gt;--   $1 = 101         (bigint, structure id of Age)&lt;/span&gt;
&lt;span class="c1"&gt;--   $2 = 102         (bigint, structure id of City)&lt;/span&gt;
&lt;span class="c1"&gt;--   $3 = 42          (bigint, scheme id)&lt;/span&gt;
&lt;span class="c1"&gt;--   $4 = {101, 102}  (bigint[])&lt;/span&gt;
&lt;span class="c1"&gt;--   $5 = 30          (bigint)&lt;/span&gt;
&lt;span class="c1"&gt;--   $6 = 'London'    (text)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What this gets you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;One index hit&lt;/strong&gt; on &lt;code&gt;(_id_structure, _id_object)&lt;/code&gt; — &lt;code&gt;_id_structure = ANY($4)&lt;/code&gt; uses the structure-id covering index, narrowing to only the props the query touches.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;One GROUP BY pass&lt;/strong&gt; — &lt;code&gt;FILTER (WHERE …)&lt;/code&gt; is PostgreSQL's way of splitting values into columns &lt;em&gt;during&lt;/em&gt; aggregation. Each scalar pivot field costs one &lt;code&gt;array_agg&lt;/code&gt; aggregator, all running in the same scan. The &lt;code&gt;[1]&lt;/code&gt; subscript picks the single element because scalar fields have at most one &lt;code&gt;_values&lt;/code&gt; row.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Outer WHERE on flat columns&lt;/strong&gt; — &lt;code&gt;pvt."Age" &amp;gt; $5&lt;/code&gt; is a normal B-tree comparison on the CTE's projected columns. The optimizer reorders these freely.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Stable parameterized SQL text&lt;/strong&gt; — same &lt;code&gt;$1..$N&lt;/code&gt; placeholders every call, so PostgreSQL's prepared-statement plan cache works. Connection pools love this.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;5 fields or 50 — same query shape, same cost class.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;And here is the same query through Free 3.0.0 on PostgreSQL — byte-for-byte the same shape:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Free 3.0.0 on PostgreSQL — emitted by pvt_build_query_sql(scheme, facets_jsonb)&lt;/span&gt;
&lt;span class="k"&gt;WITH&lt;/span&gt; &lt;span class="n"&gt;pvt_cte&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id_object&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;array_agg&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_Long&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;   &lt;span class="n"&gt;FILTER&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id_structure&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;101&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_array_index&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;))[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="nv"&gt;"Age"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;array_agg&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_String&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;FILTER&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id_structure&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;102&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_array_index&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;))[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="nv"&gt;"City"&lt;/span&gt;
  &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;_values&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;
  &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;_id&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;_objects&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;_id_scheme&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;42&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id_object&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id&lt;/span&gt;
  &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id_structure&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;101&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;102&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id_object&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;_objects&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;
&lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;pvt_cte&lt;/span&gt; &lt;span class="n"&gt;pvt&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pvt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id_object&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;pvt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"Age"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;pvt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"City"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'London'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;-- The literals you see here are NOT string-concatenated user input.&lt;/span&gt;
&lt;span class="c1"&gt;-- They are emitted by format(..., %L) with explicit type casts inside&lt;/span&gt;
&lt;span class="c1"&gt;-- the plpgsql builder. The C# caller made two parameterized roundtrips:&lt;/span&gt;
&lt;span class="c1"&gt;--   1) SELECT pvt_build_query_sql($1, $2::jsonb, $3, $4)   -- returns SQL text&lt;/span&gt;
&lt;span class="c1"&gt;--        with ($1=schemeId, $2=facets, $3=limit, $4=offset) as Npgsql parameters&lt;/span&gt;
&lt;span class="c1"&gt;--   2) EXECUTE the returned text on the connection → collect _id values&lt;/span&gt;
&lt;span class="c1"&gt;--   3) SELECT * FROM get_objects_json($1::bigint[], $2)    -- hydrate by ids&lt;/span&gt;
&lt;span class="c1"&gt;--        with ($1={...matched ids...}, $2=maxDepth) as Npgsql parameters&lt;/span&gt;
&lt;span class="c1"&gt;-- See the SQL-injection section below for the full chain.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Same CTE, same &lt;code&gt;array_agg FILTER&lt;/code&gt;, same outer flat WHERE. The only structural difference: the SQL text is built once per call by &lt;code&gt;pvt_build_query_sql&lt;/code&gt; inside the database, so the &lt;em&gt;outer&lt;/em&gt; &lt;code&gt;EXECUTE&lt;/code&gt; runs a unique string each call — values come baked in as &lt;code&gt;%L&lt;/code&gt;-quoted literals with type casts (&lt;code&gt;30&lt;/code&gt;, &lt;code&gt;'London'&lt;/code&gt;) rather than as &lt;code&gt;$N&lt;/code&gt; placeholders. That has plan-cache implications versus Pro (the SQL text varies, so PG can't reuse a plan across calls with different values), but the &lt;em&gt;index path&lt;/em&gt; and &lt;em&gt;join shape&lt;/em&gt; the planner picks are identical. For the parameterized story, see how this gets called from any language a few sections down.&lt;/p&gt;

&lt;h3&gt;
  
  
  Free path (until 3.0.0) — interpreted on the database side
&lt;/h3&gt;

&lt;p&gt;The Free path serialized the LINQ filter into a JSON facet structure, shipped it to a plpgsql function, and that function built dynamic SQL inside the database. For the same LINQ above:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// C# side just packs the facets and calls one function&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;facets&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;@and&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="kt"&gt;object&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;Age&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;gt&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;30&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;City&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;eq&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"London"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;QueryAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"SELECT * FROM search_objects_with_facets($1, $2)"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                      &lt;span class="n"&gt;schemeId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;facets&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The function then emitted, for those exact arguments:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Old Free on PostgreSQL — N props in WHERE → N correlated EXISTS&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;_objects&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id_scheme&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;42&lt;/span&gt;
  &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="k"&gt;EXISTS&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;_values&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;
    &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id_object&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id&lt;/span&gt;
      &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id_structure&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;101&lt;/span&gt;
      &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_Long&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="k"&gt;EXISTS&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;_values&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;
    &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id_object&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id&lt;/span&gt;
      &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id_structure&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;102&lt;/span&gt;
      &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_String&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'London'&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;PostgreSQL optimizes correlated &lt;code&gt;EXISTS&lt;/code&gt; well when the right index is there, so this is &lt;em&gt;fine&lt;/em&gt; at 1–3 props. By 10 props the planner is doing real work to choose join order. By 30 props you're seeing query times you don't want. (For curiosity: the legacy path stays in the database under the name &lt;code&gt;search_objects_with_facets()&lt;/code&gt; for back-compat — old callers keep working.)&lt;/p&gt;

&lt;p&gt;On MSSql Free the picture was uglier still — a wide-CASE inline pivot that hadn't received the same iterations as the PG side. Different SQL shape from PG, different code path inside the library, different bugs. The two Free implementations had drifted.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why the gap existed for so long
&lt;/h3&gt;

&lt;p&gt;I built the Pro engine because we needed it. We use Pro internally — that's what runs the production workloads. The Free path kept working for its users, but parity was always "next quarter." It's not a single feature, it's:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;pivot CTE generation for both dialects;&lt;/li&gt;
&lt;li&gt;base-field pushdown into the inner &lt;code&gt;_objects&lt;/code&gt; subquery;&lt;/li&gt;
&lt;li&gt;nested-dict accessor (&lt;code&gt;Field[key].Child&lt;/code&gt;);&lt;/li&gt;
&lt;li&gt;ListItem &lt;code&gt;.Value&lt;/code&gt; / &lt;code&gt;.Alias&lt;/code&gt; via a single JOIN;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;GroupBy&lt;/code&gt;, &lt;code&gt;HAVING&lt;/code&gt;, &lt;code&gt;ArrayGroupBy&lt;/code&gt;, &lt;code&gt;DistinctBy&lt;/code&gt;, &lt;code&gt;Sql.Function&lt;/code&gt; whitelist, &lt;code&gt;$expr&lt;/code&gt; trees;&lt;/li&gt;
&lt;li&gt;null semantics, &lt;code&gt;$exists&lt;/code&gt; / &lt;code&gt;$notNull&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;a filter-splitting optimizer that decides what's a Shape A (pure base), B (narrow) or C (wide pivot) query;&lt;/li&gt;
&lt;li&gt;and all of it round-tripping through 200+ integration tests per backend.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Spread that across two databases and you understand why it kept slipping. The interest spike from the launch posts is what pushed me to drop the half-written stuff and ship this. It's not finished-finished — that's why it's an OSS release instead of a private polish round.&lt;/p&gt;




&lt;h2&gt;
  
  
  What 3.0.0 actually changes
&lt;/h2&gt;

&lt;p&gt;The headline: &lt;strong&gt;Free and Pro now emit the same PVT CTE shape&lt;/strong&gt;, on both PostgreSQL and MSSql.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. MSSql Free hit full v2-pvt parity — 145/145
&lt;/h3&gt;

&lt;p&gt;The old MSSql Free path is gone. The new one is a 27-file SQL module under &lt;a href="https://github.com/redbase-app/redb/tree/main/redb.MSSql/sql/v2-pvt" rel="noopener noreferrer"&gt;&lt;code&gt;redb.MSSql/sql/v2-pvt/&lt;/code&gt;&lt;/a&gt;, assembled by an MSBuild target into a single &lt;code&gt;pvt_bundle.sql&lt;/code&gt; resource embedded in &lt;code&gt;RedBase.MSSql.dll&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;00_module_init.sql           -- version sentinel, drop-and-replace bootstrap
10_pvt_field_collection.sql  -- walks the facet JSON, harvests structure ids
13_pvt_condition.sql         -- per-field WHERE fragments
14_pvt_where.sql             -- recursive $and/$or/$expr walker
15_pvt_order.sql             -- ORDER BY building (incl. $expr ordering)
16_pvt_split.sql             -- Shape A/B/C classifier, pushdown engine
17_pvt_expr.sql              -- $expr classifier + scalar expression compiler
20_pvt_build_query_sql.sql   -- the entry point — returns the final SQL text
99_smoke_auto.sql            -- 195 PASS / 0 FAIL / 1 SKIP regression suite
... (and friends)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The MSSql dialect uses &lt;code&gt;MAX(CASE WHEN …)&lt;/code&gt; instead of &lt;code&gt;array_agg FILTER&lt;/code&gt; (SQL Server has no FILTER clause), but the rest of the shape is the same. Same LINQ as above, on MSSql Free, becomes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Free MSSql 3.0.0 — emitted by the T-SQL builder under [dbo].[pvt_build_query_sql]&lt;/span&gt;
&lt;span class="k"&gt;WITH&lt;/span&gt; &lt;span class="n"&gt;pvt_cte&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.[&lt;/span&gt;&lt;span class="n"&gt;_id_object&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="k"&gt;MAX&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;CASE&lt;/span&gt; &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.[&lt;/span&gt;&lt;span class="n"&gt;_id_structure&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;101&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.[&lt;/span&gt;&lt;span class="n"&gt;_array_index&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;THEN&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.[&lt;/span&gt;&lt;span class="n"&gt;_Long&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;   &lt;span class="k"&gt;END&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Age&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="k"&gt;MAX&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;CASE&lt;/span&gt; &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.[&lt;/span&gt;&lt;span class="n"&gt;_id_structure&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;102&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.[&lt;/span&gt;&lt;span class="n"&gt;_array_index&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;THEN&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.[&lt;/span&gt;&lt;span class="n"&gt;_String&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;END&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;City&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;_values&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;
  &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;_id&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;_objects&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;_id_scheme&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;42&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;
    &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.[&lt;/span&gt;&lt;span class="n"&gt;_id_object&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.[&lt;/span&gt;&lt;span class="n"&gt;_id&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.[&lt;/span&gt;&lt;span class="n"&gt;_id_structure&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;101&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;102&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.[&lt;/span&gt;&lt;span class="n"&gt;_id_object&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;_objects&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;
&lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;pvt_cte&lt;/span&gt; &lt;span class="n"&gt;pvt&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.[&lt;/span&gt;&lt;span class="n"&gt;_id&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pvt&lt;/span&gt;&lt;span class="p"&gt;.[&lt;/span&gt;&lt;span class="n"&gt;_id_object&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;pvt&lt;/span&gt;&lt;span class="p"&gt;.[&lt;/span&gt;&lt;span class="n"&gt;Age&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;pvt&lt;/span&gt;&lt;span class="p"&gt;.[&lt;/span&gt;&lt;span class="n"&gt;City&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;N&lt;/span&gt;&lt;span class="s1"&gt;'London'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;-- Same idea as the PG Free block above: literals are inlined by the T-SQL&lt;/span&gt;
&lt;span class="c1"&gt;-- builder via QUOTENAME() for identifiers and quoted-literal emission for&lt;/span&gt;
&lt;span class="c1"&gt;-- values (numeric literals stay bare, strings become N'...' with embedded&lt;/span&gt;
&lt;span class="c1"&gt;-- quotes escaped). The C# caller made two parameterized roundtrips:&lt;/span&gt;
&lt;span class="c1"&gt;--   1) EXEC sp_executesql N'SELECT [dbo].[pvt_build_query_sql](@scheme, @facets, @limit, @offset)',&lt;/span&gt;
&lt;span class="c1"&gt;--        N'@scheme bigint, @facets nvarchar(max), @limit int, @offset int', ...   -- returns SQL text&lt;/span&gt;
&lt;span class="c1"&gt;--   2) EXEC sp_executesql @sql                                                    -- collect _id values&lt;/span&gt;
&lt;span class="c1"&gt;--   3) SELECT * FROM [dbo].[get_objects_json](@ids, @maxDepth)                    -- hydrate by ids&lt;/span&gt;
&lt;span class="c1"&gt;-- User input never touches a SQL parser unquoted. The SQL-injection section&lt;/span&gt;
&lt;span class="c1"&gt;-- below walks the chain.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;MAX(CASE WHEN ...)&lt;/code&gt; is the SQL Server idiom that mirrors &lt;code&gt;array_agg FILTER&lt;/code&gt;: NULLs are ignored by aggregates, so the CASE acts as a "pass through if matching, otherwise null" gate. Same one-pass GROUP BY, same outer WHERE on flat columns. (Unlike Pro — which sends a parameterized statement with &lt;code&gt;@p1..@pN&lt;/code&gt; placeholders and benefits from SQL Server's plan cache — Free's text varies per call, so plan reuse depends on SQL Server's auto-parameterization. Shape and index path are identical to Pro either way.)&lt;/p&gt;

&lt;p&gt;Everything PG Free had now works identically on MSSql Free: flat + tree queries, scalar / array / dict fields, nested-dict &lt;code&gt;Field[key].Child&lt;/code&gt;, ListItem joins for &lt;code&gt;.Id&lt;/code&gt; / &lt;code&gt;.Value&lt;/code&gt; / &lt;code&gt;.Alias&lt;/code&gt;, same-scheme nested POCO compound paths, &lt;code&gt;OrderBy&lt;/code&gt; / &lt;code&gt;Take&lt;/code&gt; / &lt;code&gt;Skip&lt;/code&gt; / &lt;code&gt;DistinctBy&lt;/code&gt;, full &lt;code&gt;GroupBy&lt;/code&gt; with &lt;code&gt;HAVING&lt;/code&gt;, &lt;code&gt;ArrayGroupBy&lt;/code&gt; via &lt;code&gt;OUTER APPLY&lt;/code&gt;, array aggregates, &lt;code&gt;Sql.Function&lt;/code&gt; whitelist, &lt;code&gt;$expr&lt;/code&gt;, null semantics.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Auto-deploy of the v2-pvt bundle
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;ISqlDialect&lt;/code&gt; got a new method:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;ISqlDialect&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// ... existing dialect surface ...&lt;/span&gt;
    &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nf"&gt;Query_PvtRequiredVersion&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;   &lt;span class="c1"&gt;// semver the embedded bundle ships&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;RedbServiceBase.EnsurePvtModuleDeployedAsync&lt;/code&gt; runs on &lt;code&gt;InitializeAsync()&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;required&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Dialect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Query_PvtRequiredVersion&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;required&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;                 &lt;span class="c1"&gt;// dialect doesn't use v2-pvt&lt;/span&gt;

&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;deployed&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ExecuteScalarAsync&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;?&amp;gt;(&lt;/span&gt;
    &lt;span class="n"&gt;Dialect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SelectPvtModuleVersionSql&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;     &lt;span class="c1"&gt;// calls pvt_module_version()&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Equals&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;deployed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;required&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;StringComparison&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Ordinal&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;                                    &lt;span class="c1"&gt;// exact match — already deployed&lt;/span&gt;

&lt;span class="n"&gt;Logger&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;LogInformation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"v2-pvt mismatch (deployed={D}, required={R}). Applying bundle."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                       &lt;span class="n"&gt;deployed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;required&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ExecuteNonQueryAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;ReadEmbeddedBundle&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The match is &lt;strong&gt;exact-string&lt;/strong&gt;, not semver-numeric. Any change to any of the 27 source files bumps the bundle version, the MSBuild target regenerates &lt;code&gt;pvt_bundle.sql&lt;/code&gt; on the next build, and on next service startup the new bundle gets applied. No DBA in the loop, no "did you forget to run the migration?" tickets.&lt;/p&gt;

&lt;p&gt;(One MSBuild gotcha cost me half a day: declaring &lt;code&gt;&amp;lt;EmbeddedResource Include="sql\v2-pvt\pvt_bundle.sql" /&amp;gt;&lt;/code&gt; silently renames the manifest resource from &lt;code&gt;redb.MSSql.sql.v2-pvt.pvt_bundle.sql&lt;/code&gt; to &lt;code&gt;redb.MSSql.sql.v2_pvt.pvt_bundle.sql&lt;/code&gt; — dashes become underscores. &lt;code&gt;GetManifestResourceStream&lt;/code&gt; returns &lt;code&gt;null&lt;/code&gt; and you stare at it for a while. Fix: pin &lt;code&gt;&amp;lt;LogicalName&amp;gt;redb.MSSql.sql.v2-pvt.pvt_bundle.sql&amp;lt;/LogicalName&amp;gt;&lt;/code&gt; explicitly. Free tip if you ever ship SQL through embedded resources.)&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Base-field pushdown — the 894 ms → 11 ms one
&lt;/h3&gt;

&lt;p&gt;This is the regression that originally motivated &lt;code&gt;2.0.1&lt;/code&gt;. When a query mixes a base-field predicate (on &lt;code&gt;_objects&lt;/code&gt; columns) with a props predicate:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;children&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;service&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Query&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;EmployeeProps&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WhereRedb&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;o&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id_parent&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="m"&gt;555&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;   &lt;span class="c1"&gt;// base field, manager id 555&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Age&lt;/span&gt; &lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="m"&gt;30&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;                 &lt;span class="c1"&gt;// props field&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToListAsync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The naive layout puts the base predicate in the outer WHERE — that's what &lt;code&gt;2.0.0&lt;/code&gt; did:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Bad: 894 ms on a scheme with millions of _values rows&lt;/span&gt;
&lt;span class="k"&gt;WITH&lt;/span&gt; &lt;span class="n"&gt;pvt_cte&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id_object&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;array_agg&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_Long&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;FILTER&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id_structure&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;))[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="nv"&gt;"Age"&lt;/span&gt;
  &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;_values&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;
  &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;_objects&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id_object&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id&lt;/span&gt;
  &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id_scheme&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id_structure&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;ANY&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id_object&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;_objects&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;
&lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;pvt_cte&lt;/span&gt; &lt;span class="n"&gt;pvt&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pvt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id_object&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id_parent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;    &lt;span class="c1"&gt;-- outer: applies AFTER the CTE pivoted the entire scheme&lt;/span&gt;
  &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;pvt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"Age"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;-- $1=101, $2=42, $3={101}, $4=555, $5=30&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;PostgreSQL aggregates &lt;code&gt;_values&lt;/code&gt; for &lt;em&gt;every&lt;/em&gt; employee in the scheme and only then narrows by &lt;code&gt;_id_parent&lt;/code&gt;. Self-inflicted full pivot.&lt;/p&gt;

&lt;p&gt;3.0.0 (both Free and Pro, both dialects) classifies the base predicate at compile time and injects it &lt;em&gt;into the inner &lt;code&gt;_objects&lt;/code&gt; subquery&lt;/em&gt; of the PVT CTE:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Good: 11 ms&lt;/span&gt;
&lt;span class="k"&gt;WITH&lt;/span&gt; &lt;span class="n"&gt;pvt_cte&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id_object&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;array_agg&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_Long&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;FILTER&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id_structure&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;))[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="nv"&gt;"Age"&lt;/span&gt;
  &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;_values&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;
  &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;_id&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;_objects&lt;/span&gt;
        &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;_id_scheme&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;
          &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;_id_parent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;   &lt;span class="c1"&gt;-- pushed in&lt;/span&gt;
       &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id_object&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id&lt;/span&gt;
  &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id_structure&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;ANY&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id_object&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;_objects&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;
&lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;pvt_cte&lt;/span&gt; &lt;span class="n"&gt;pvt&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pvt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id_object&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;pvt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"Age"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;-- $1=101, $2=42, $3=555, $4={101}, $5=30&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Free 3.0.0 emits the same shape — here it is on PostgreSQL, with the values baked in by &lt;code&gt;pvt_split_filter&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Free 3.0.0 on PostgreSQL — pushdown identical to Pro&lt;/span&gt;
&lt;span class="k"&gt;WITH&lt;/span&gt; &lt;span class="n"&gt;pvt_cte&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id_object&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;array_agg&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_Long&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;FILTER&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id_structure&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;101&lt;/span&gt;&lt;span class="p"&gt;))[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="nv"&gt;"Age"&lt;/span&gt;
  &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;_values&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;
  &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;_id&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;_objects&lt;/span&gt;
        &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;_id_scheme&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;42&lt;/span&gt;
          &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;_id_parent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;555&lt;/span&gt;           &lt;span class="c1"&gt;-- pushed in by pvt_split_filter&lt;/span&gt;
       &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id_object&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id&lt;/span&gt;
  &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id_structure&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;101&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id_object&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;_objects&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;
&lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;pvt_cte&lt;/span&gt; &lt;span class="n"&gt;pvt&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pvt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id_object&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;pvt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"Age"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And on MSSql Free 3.0.0 — again, literals inlined by the T-SQL builder, not parameters:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Free MSSql 3.0.0 — pushdown via the same classifier, MAX/CASE pivot, values inlined&lt;/span&gt;
&lt;span class="k"&gt;WITH&lt;/span&gt; &lt;span class="n"&gt;pvt_cte&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.[&lt;/span&gt;&lt;span class="n"&gt;_id_object&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="k"&gt;MAX&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;CASE&lt;/span&gt; &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.[&lt;/span&gt;&lt;span class="n"&gt;_id_structure&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;101&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.[&lt;/span&gt;&lt;span class="n"&gt;_array_index&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;THEN&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.[&lt;/span&gt;&lt;span class="n"&gt;_Long&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;END&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Age&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;_values&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;
  &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;_id&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;_objects&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;_id_scheme&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;42&lt;/span&gt;
          &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;_id_parent&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;555&lt;/span&gt;         &lt;span class="c1"&gt;-- pushed in by the T-SQL split classifier&lt;/span&gt;
       &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.[&lt;/span&gt;&lt;span class="n"&gt;_id_object&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.[&lt;/span&gt;&lt;span class="n"&gt;_id&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.[&lt;/span&gt;&lt;span class="n"&gt;_id_structure&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;101&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.[&lt;/span&gt;&lt;span class="n"&gt;_id_object&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;_objects&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;
&lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;pvt_cte&lt;/span&gt; &lt;span class="n"&gt;pvt&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.[&lt;/span&gt;&lt;span class="n"&gt;_id&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pvt&lt;/span&gt;&lt;span class="p"&gt;.[&lt;/span&gt;&lt;span class="n"&gt;_id_object&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;pvt&lt;/span&gt;&lt;span class="p"&gt;.[&lt;/span&gt;&lt;span class="n"&gt;Age&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The inner subquery resolves via the &lt;code&gt;(_id_scheme, _id_parent)&lt;/code&gt; covering index, returns ~N matching &lt;code&gt;_id&lt;/code&gt;s, and the pivot only sees &lt;code&gt;_values&lt;/code&gt; rows for those N objects. Pro got this in &lt;code&gt;2.0.1&lt;/code&gt;; 3.0.0 brings the same to Free through &lt;a href="https://github.com/redbase-app/redb/blob/main/redb.MSSql/sql/v2-pvt/16_pvt_split.sql" rel="noopener noreferrer"&gt;&lt;code&gt;pvt_split_filter&lt;/code&gt;&lt;/a&gt; and the &lt;code&gt;pvt_expr_is_base_only&lt;/code&gt; classifier in &lt;code&gt;17_pvt_expr.sql&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Nested-dict accessor short-circuit
&lt;/h3&gt;

&lt;p&gt;For &lt;code&gt;Dictionary&amp;lt;string, T&amp;gt;&lt;/code&gt; fields, &lt;code&gt;Where(o =&amp;gt; o.OfficeLocations["HQ"].City == "New York")&lt;/code&gt; builds the dict-keyed pivot column in the CTE. There's no separate &lt;code&gt;_dict_key&lt;/code&gt; column in &lt;code&gt;_values&lt;/code&gt; — the existing &lt;code&gt;_array_index&lt;/code&gt; slot (&lt;code&gt;text&lt;/code&gt;) doubles as the dict key for dictionary fields (it stays an integer-as-text for arrays, holds the key for dicts, is &lt;code&gt;NULL&lt;/code&gt; for scalars):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;array_agg&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_String&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;FILTER&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id_structure&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;117&lt;/span&gt;    &lt;span class="c1"&gt;-- structure id of City inside OfficeLocations&lt;/span&gt;
    &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_array_index&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'HQ'&lt;/span&gt;    &lt;span class="c1"&gt;-- _array_index doubles as the dict key&lt;/span&gt;
&lt;span class="p"&gt;))[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="nv"&gt;"OfficeLocations[HQ].City"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The bug fixed in this cycle (MSSql 0.1.3, PG 0.6.1 earlier): the outer WHERE was &lt;em&gt;also&lt;/em&gt; emitting a separate &lt;code&gt;EXISTS&lt;/code&gt; over &lt;code&gt;_values&lt;/code&gt; to re-check the same dict key, even though the CTE had already done that work. The six-line fix in &lt;a href="https://github.com/redbase-app/redb/blob/main/redb.MSSql/sql/v2-pvt/13_pvt_condition.sql" rel="noopener noreferrer"&gt;&lt;code&gt;13_pvt_condition.sql&lt;/code&gt;&lt;/a&gt; makes the outer WHERE just reference the pivot column directly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Before&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="k"&gt;EXISTS&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;_values&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;
  &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id_object&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id&lt;/span&gt;
    &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id_structure&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;117&lt;/span&gt;
    &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_array_index&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'HQ'&lt;/span&gt;
    &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_String&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'New York'&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;-- After&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;pvt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"OfficeLocations[HQ].City"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;    &lt;span class="c1"&gt;-- $4='New York'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One predicate, no subquery, the optimizer can reorder it freely with the rest of the WHERE.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. What landed in Pro this cycle
&lt;/h3&gt;

&lt;p&gt;While Free was catching up, Pro added its own things (the version is &lt;code&gt;3.0.0&lt;/code&gt; for both):&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;GroupBy + HAVING&lt;/code&gt; in Pro.&lt;/strong&gt; &lt;code&gt;HavingAsync&lt;/code&gt; existed in Free since &lt;code&gt;1.2.x&lt;/code&gt; but had no Pro counterpart. Now:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;stats&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;service&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Query&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;EmployeeProps&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GroupBy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Department&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;g&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;g&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Count&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt; &lt;span class="p"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;g&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Average&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Age&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt; &lt;span class="m"&gt;40&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;   &lt;span class="c1"&gt;// HAVING&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;g&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;Dept&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;g&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;N&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;g&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Count&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToListAsync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;→&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;WITH&lt;/span&gt; &lt;span class="n"&gt;pvt_cte&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id_object&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;array_agg&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_listitem&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;FILTER&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id_structure&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;))[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="nv"&gt;"Department_id"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;array_agg&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_Long&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;     &lt;span class="n"&gt;FILTER&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id_structure&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;))[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="nv"&gt;"Age"&lt;/span&gt;
  &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;_values&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;
  &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;_id&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;_objects&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;_id_scheme&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id_object&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id&lt;/span&gt;
  &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id_structure&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;ANY&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id_object&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;li&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"_value"&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="nv"&gt;"Dept"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="nv"&gt;"N"&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;_objects&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;
&lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;pvt_cte&lt;/span&gt; &lt;span class="n"&gt;pvt&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pvt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id_object&lt;/span&gt;
&lt;span class="k"&gt;LEFT&lt;/span&gt; &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;_list_items&lt;/span&gt; &lt;span class="n"&gt;li&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;li&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pvt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"Department_id"&lt;/span&gt;
&lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;li&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"_value"&lt;/span&gt;
&lt;span class="k"&gt;HAVING&lt;/span&gt; &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="k"&gt;AVG&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pvt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"Age"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;-- $1=130, $2=101, $3=42, $4={130,101}, $5=10, $6=40&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Free side has had &lt;code&gt;HavingAsync&lt;/code&gt; since &lt;code&gt;1.2.x&lt;/code&gt; — here's the same LINQ through Free 3.0.0 on PostgreSQL for parity, so you can see they really do meet in the middle now:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Free 3.0.0 PG — same HAVING shape, values inlined by pvt_build_query_sql&lt;/span&gt;
&lt;span class="k"&gt;WITH&lt;/span&gt; &lt;span class="n"&gt;pvt_cte&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id_object&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;array_agg&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_listitem&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;FILTER&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id_structure&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;130&lt;/span&gt;&lt;span class="p"&gt;))[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="nv"&gt;"Department_id"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;array_agg&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_Long&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;     &lt;span class="n"&gt;FILTER&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id_structure&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;101&lt;/span&gt;&lt;span class="p"&gt;))[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="nv"&gt;"Age"&lt;/span&gt;
  &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;_values&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;
  &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;_id&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;_objects&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;_id_scheme&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;42&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id_object&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id&lt;/span&gt;
  &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id_structure&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;130&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;101&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id_object&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;li&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"_value"&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="nv"&gt;"Dept"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="nv"&gt;"N"&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;_objects&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;
&lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;pvt_cte&lt;/span&gt; &lt;span class="n"&gt;pvt&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pvt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id_object&lt;/span&gt;
&lt;span class="k"&gt;LEFT&lt;/span&gt; &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;_list_items&lt;/span&gt; &lt;span class="n"&gt;li&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;li&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pvt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"Department_id"&lt;/span&gt;
&lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;li&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"_value"&lt;/span&gt;
&lt;span class="k"&gt;HAVING&lt;/span&gt; &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="k"&gt;AVG&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pvt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"Age"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;40&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And on MSSql Free 3.0.0 — same shape, literals inlined:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Free MSSql 3.0.0 — same HAVING shape, MAX/CASE pivot, values inlined by the T-SQL builder&lt;/span&gt;
&lt;span class="k"&gt;WITH&lt;/span&gt; &lt;span class="n"&gt;pvt_cte&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.[&lt;/span&gt;&lt;span class="n"&gt;_id_object&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="k"&gt;MAX&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;CASE&lt;/span&gt; &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.[&lt;/span&gt;&lt;span class="n"&gt;_id_structure&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;130&lt;/span&gt; &lt;span class="k"&gt;THEN&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.[&lt;/span&gt;&lt;span class="n"&gt;_listitem&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;END&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Department_id&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="k"&gt;MAX&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;CASE&lt;/span&gt; &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.[&lt;/span&gt;&lt;span class="n"&gt;_id_structure&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;101&lt;/span&gt; &lt;span class="k"&gt;THEN&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.[&lt;/span&gt;&lt;span class="n"&gt;_Long&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;     &lt;span class="k"&gt;END&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Age&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;_values&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;
  &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;_id&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;_objects&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;_id_scheme&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;42&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;
    &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.[&lt;/span&gt;&lt;span class="n"&gt;_id_object&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.[&lt;/span&gt;&lt;span class="n"&gt;_id&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.[&lt;/span&gt;&lt;span class="n"&gt;_id_structure&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;130&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;101&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.[&lt;/span&gt;&lt;span class="n"&gt;_id_object&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;li&lt;/span&gt;&lt;span class="p"&gt;.[&lt;/span&gt;&lt;span class="n"&gt;_value&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Dept&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;N&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;_objects&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;
&lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;pvt_cte&lt;/span&gt; &lt;span class="n"&gt;pvt&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.[&lt;/span&gt;&lt;span class="n"&gt;_id&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pvt&lt;/span&gt;&lt;span class="p"&gt;.[&lt;/span&gt;&lt;span class="n"&gt;_id_object&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="k"&gt;LEFT&lt;/span&gt; &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;_list_items&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="n"&gt;li&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;li&lt;/span&gt;&lt;span class="p"&gt;.[&lt;/span&gt;&lt;span class="n"&gt;_id&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pvt&lt;/span&gt;&lt;span class="p"&gt;.[&lt;/span&gt;&lt;span class="n"&gt;Department_id&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;li&lt;/span&gt;&lt;span class="p"&gt;.[&lt;/span&gt;&lt;span class="n"&gt;_value&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="k"&gt;HAVING&lt;/span&gt; &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="k"&gt;AVG&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pvt&lt;/span&gt;&lt;span class="p"&gt;.[&lt;/span&gt;&lt;span class="n"&gt;Age&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;40&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;33/33 HAVING + 6/6 no-HAVING in &lt;code&gt;GroupByHavingTestsBase&lt;/code&gt;, green on PG.Pro and MSSql.Pro — and identical row counts on the Free side.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;ArrayGroupBy&lt;/code&gt; unified&lt;/strong&gt; across all four tiers — &lt;code&gt;GroupBy(items =&amp;gt; items.SelectMany(o =&amp;gt; o.Skills))&lt;/code&gt; works identically on PG Free, PG.Pro, MSSql Free, MSSql.Pro.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;MSSql.Pro &lt;code&gt;AggregateBatch&lt;/code&gt; parity with PG.Pro&lt;/strong&gt; — non-numeric &lt;code&gt;MinAsync&lt;/code&gt; / &lt;code&gt;MaxAsync&lt;/code&gt; (over &lt;code&gt;string&lt;/code&gt;/&lt;code&gt;DateTime&lt;/code&gt;/&lt;code&gt;Guid&lt;/code&gt;) and a &lt;code&gt;Where(...)&lt;/code&gt; filter inside a batch aggregation now produce the same plan shape on MSSql.Pro as PG.Pro.&lt;/p&gt;

&lt;p&gt;Plus a stack of bug-fixes documented in &lt;a href="https://github.com/redbase-app/redb/blob/main/CHANGELOG.md" rel="noopener noreferrer"&gt;CHANGELOG.md&lt;/a&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  "Wait — Free builds SQL dynamically inside plpgsql. What about SQL injection?"
&lt;/h2&gt;

&lt;p&gt;Right question. If you skimmed the Free SQL above and noticed it's &lt;code&gt;EXECUTE&lt;/code&gt;-ing a dynamically-built string inside a database function — your instinct is correct, that &lt;em&gt;would&lt;/em&gt; be a problem in a naively-written generator. Here's what actually guards against it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Pro side (C#) — boring and safe
&lt;/h3&gt;

&lt;p&gt;Pro builds parameterized SQL via &lt;code&gt;SqlParameterCollector&lt;/code&gt;. Every value the user expression captured (&lt;code&gt;30&lt;/code&gt;, &lt;code&gt;"London"&lt;/code&gt;, &lt;code&gt;managerId&lt;/code&gt;) becomes an &lt;code&gt;NpgsqlParameter&lt;/code&gt; / &lt;code&gt;SqlParameter&lt;/code&gt;. The text Npgsql sends to PostgreSQL contains &lt;code&gt;$1..$N&lt;/code&gt; placeholders only; values travel out-of-band through the wire protocol and never see a SQL parser. No template, no concatenation, no &lt;code&gt;format()&lt;/code&gt;. There's literally no path for an injection because there's no string-to-SQL bridge for values.&lt;/p&gt;

&lt;h3&gt;
  
  
  Free side on PostgreSQL (plpgsql) — three layers, on purpose
&lt;/h3&gt;

&lt;p&gt;Free has it harder than Pro because the SQL builder runs &lt;em&gt;inside&lt;/em&gt; the database, so the values arrive baked into the JSON. The defenses, layer by layer:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Values pass through &lt;code&gt;format(..., %L)&lt;/code&gt; with explicit type casts.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;%L&lt;/code&gt; is PostgreSQL's literal-quoting format specifier — same semantics as &lt;code&gt;quote_literal()&lt;/code&gt;. It single-quotes the value, escapes embedded quotes, and returns a SQL-safe literal. Example from &lt;a href="https://github.com/redbase-app/redb/blob/main/redb.Postgres/sql/v2-pvt/04_pvt_inner_condition.sql" rel="noopener noreferrer"&gt;&lt;code&gt;04_pvt_inner_condition.sql&lt;/code&gt;&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- The leaf emitter for "Long $gt N" looks like this:&lt;/span&gt;
&lt;span class="k"&gt;RETURN&lt;/span&gt; &lt;span class="n"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'fv._Long %s %L::bigint'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;op_symbol&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;operator_value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If a malicious payload arrives as &lt;code&gt;operator_value = "1; DROP TABLE _objects --"&lt;/code&gt;, &lt;code&gt;%L&lt;/code&gt; produces:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="n"&gt;fv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_Long&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'1; DROP TABLE _objects --'&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nb"&gt;bigint&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;::bigint&lt;/code&gt; cast then fails at parse time with a normal type error. The single quotes from &lt;code&gt;%L&lt;/code&gt; make the injection inert; the type cast makes it a hard error rather than silent data corruption. Every value branch in &lt;code&gt;04_pvt_inner_condition.sql&lt;/code&gt; uses this pattern: &lt;code&gt;%L::bigint&lt;/code&gt;, &lt;code&gt;%L::numeric&lt;/code&gt;, &lt;code&gt;%L::timestamptz&lt;/code&gt;, &lt;code&gt;%L::uuid&lt;/code&gt;, &lt;code&gt;%L::boolean&lt;/code&gt;, &lt;code&gt;%L::interval&lt;/code&gt;. Strings use plain &lt;code&gt;%L&lt;/code&gt; (which is &lt;code&gt;'…'&lt;/code&gt;-quoting), and any further use as text is still inside single quotes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Identifiers come from the database, not from the JSON.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The user's JSON facets refer to fields by name (&lt;code&gt;"Age"&lt;/code&gt;, &lt;code&gt;"OfficeLocations[HQ].City"&lt;/code&gt;). The builder doesn't concatenate that string into the SQL. Instead, &lt;code&gt;pvt_field_collection&lt;/code&gt; looks the name up in &lt;code&gt;_structures&lt;/code&gt; &lt;em&gt;for the queried scheme&lt;/em&gt; — by joining &lt;code&gt;_structures._name = $jsonKey AND _structures._id_scheme = $scheme&lt;/code&gt;. If the name doesn't exist, the field is rejected. If it does exist, what gets emitted is the matched &lt;code&gt;_id_structure&lt;/code&gt; (a &lt;code&gt;bigint&lt;/code&gt;, always safe) and a pivot column alias built with &lt;code&gt;quote_ident(structure_name_from_db)&lt;/code&gt; or &lt;code&gt;%I&lt;/code&gt;. So:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The structure id is a number from the DB.&lt;/li&gt;
&lt;li&gt;The pivot column name is &lt;code&gt;quote_ident&lt;/code&gt;'d, so &lt;code&gt;"weird name with quotes"&lt;/code&gt; becomes a double-quoted SQL identifier.&lt;/li&gt;
&lt;li&gt;Reserved bases (&lt;code&gt;0$:id&lt;/code&gt;, &lt;code&gt;0$:parent_id&lt;/code&gt;, etc.) are dispatched through a fixed CASE — unknown bases raise:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;  &lt;span class="n"&gt;RAISE&lt;/span&gt; &lt;span class="n"&gt;EXCEPTION&lt;/span&gt; &lt;span class="s1"&gt;'Unknown RedbObject base field: "%" ...'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No path lets arbitrary text become an unquoted identifier.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Operator names are a fixed whitelist.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;$eq&lt;/code&gt;, &lt;code&gt;$gt&lt;/code&gt;, &lt;code&gt;$lt&lt;/code&gt;, &lt;code&gt;$ne&lt;/code&gt;, &lt;code&gt;$in&lt;/code&gt;, &lt;code&gt;$like&lt;/code&gt;, &lt;code&gt;$ilike&lt;/code&gt;, &lt;code&gt;$arrayContains&lt;/code&gt;, &lt;code&gt;$expr&lt;/code&gt;, etc. are matched by a &lt;code&gt;CASE&lt;/code&gt;/&lt;code&gt;IF&lt;/code&gt; tree in &lt;code&gt;04_pvt_inner_condition.sql&lt;/code&gt; and friends. Unknown operators don't fall through to anything — they &lt;code&gt;RAISE EXCEPTION&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="n"&gt;RAISE&lt;/span&gt; &lt;span class="n"&gt;EXCEPTION&lt;/span&gt; &lt;span class="s1"&gt;'pvt_build_agg_expr: unsupported aggregate "%"'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;v_op&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Same for &lt;code&gt;Sql.Function&lt;/code&gt; — only names in the whitelist (&lt;code&gt;COALESCE&lt;/code&gt;, &lt;code&gt;LOWER&lt;/code&gt;, &lt;code&gt;UPPER&lt;/code&gt;, …) survive; everything else raises.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. The final &lt;code&gt;EXECUTE&lt;/code&gt; evaluates a string built only from the three pieces above.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;So even though &lt;code&gt;EXECUTE&lt;/code&gt; is involved, every byte of that string came from either:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a &lt;code&gt;quote_literal&lt;/code&gt;'d (&lt;code&gt;%L&lt;/code&gt;) value with a type cast, or&lt;/li&gt;
&lt;li&gt;a &lt;code&gt;quote_ident&lt;/code&gt;'d (&lt;code&gt;%I&lt;/code&gt;) identifier sourced from &lt;code&gt;_structures&lt;/code&gt; by id lookup, or&lt;/li&gt;
&lt;li&gt;a hardcoded operator template chosen by a whitelist switch.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That's three independent gates, and any of them is sufficient to defeat injection. The plpgsql generator is paranoid by design — &lt;code&gt;format&lt;/code&gt;/&lt;code&gt;%L&lt;/code&gt;/&lt;code&gt;%I&lt;/code&gt; are used uniformly, not as a one-off. The 99-test smoke suite (&lt;code&gt;99_smoke_auto.sql&lt;/code&gt;) includes adversarial payloads (&lt;code&gt;"; DROP TABLE …&lt;/code&gt;, embedded quotes, type-mismatched literals) and expects parse errors or rejection, not successful execution.&lt;/p&gt;

&lt;p&gt;If you want to audit the PG side: every emitter is in &lt;a href="https://github.com/redbase-app/redb/blob/main/redb.Postgres/sql/v2-pvt/04_pvt_inner_condition.sql" rel="noopener noreferrer"&gt;&lt;code&gt;04_pvt_inner_condition.sql&lt;/code&gt;&lt;/a&gt;, &lt;a href="https://github.com/redbase-app/redb/blob/main/redb.Postgres/sql/v2-pvt/05_pvt_single_facet.sql" rel="noopener noreferrer"&gt;&lt;code&gt;05_pvt_single_facet.sql&lt;/code&gt;&lt;/a&gt;, &lt;code&gt;13_pvt_condition.sql&lt;/code&gt;. Grep for &lt;code&gt;format(&lt;/code&gt; — every value site has &lt;code&gt;%L&lt;/code&gt; and a cast, every identifier site has &lt;code&gt;%I&lt;/code&gt; or &lt;code&gt;quote_ident&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Free side on MSSql (T-SQL) — the same three layers, different primitives
&lt;/h3&gt;

&lt;p&gt;SQL Server has no &lt;code&gt;format(..., %L/%I)&lt;/code&gt; and no &lt;code&gt;quote_literal/quote_ident&lt;/code&gt; shorthand, so the MSSql builder under &lt;a href="https://github.com/redbase-app/redb/tree/main/redb.MSSql/sql/v2-pvt" rel="noopener noreferrer"&gt;&lt;code&gt;redb.MSSql/sql/v2-pvt/&lt;/code&gt;&lt;/a&gt; implements each layer with the T-SQL primitives that are available. Same three gates, just spelled differently.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Values are quoted with &lt;code&gt;REPLACE(..., '''', '''''')&lt;/code&gt; and/or validated with &lt;code&gt;TRY_CAST&lt;/code&gt; &lt;em&gt;before&lt;/em&gt; they get inlined.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;For strings (including dict keys and &lt;code&gt;$expr&lt;/code&gt; literals) the builder wraps the value in &lt;code&gt;N'…'&lt;/code&gt; after doubling embedded single quotes — the SQL Server equivalent of &lt;code&gt;quote_literal&lt;/code&gt;. From the actual pivot builder (&lt;a href="https://github.com/redbase-app/redb/blob/main/redb.MSSql/sql/v2-pvt/pvt_bundle.sql" rel="noopener noreferrer"&gt;&lt;code&gt;pvt_bundle.sql&lt;/code&gt;&lt;/a&gt;, the dict-key branch):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Real emitter for dict-key match in the CTE&lt;/span&gt;
&lt;span class="c1"&gt;-- (the dict key goes into _values._array_index — same slot used for array indices)&lt;/span&gt;
&lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;dict_key_literal&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
    &lt;span class="n"&gt;N&lt;/span&gt;&lt;span class="s1"&gt;'N&lt;/span&gt;&lt;span class="se"&gt;''&lt;/span&gt;&lt;span class="s1"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="k"&gt;REPLACE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;dict_key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;N&lt;/span&gt;&lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="se"&gt;''&lt;/span&gt;&lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;N&lt;/span&gt;&lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="se"&gt;''''&lt;/span&gt;&lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;N&lt;/span&gt;&lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="se"&gt;''&lt;/span&gt;&lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;-- Then concatenated into:&lt;/span&gt;
&lt;span class="c1"&gt;--   ... AND v.[_array_index] = N'HQ' THEN v.[_String] END) AS [...]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If &lt;code&gt;@dict_key = "HQ'; DROP TABLE _values; --"&lt;/code&gt;, the &lt;code&gt;REPLACE&lt;/code&gt; doubles the embedded quote to &lt;code&gt;''&lt;/code&gt;, and the emitted SQL becomes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.[&lt;/span&gt;&lt;span class="n"&gt;_array_index&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;N&lt;/span&gt;&lt;span class="s1"&gt;'HQ&lt;/span&gt;&lt;span class="se"&gt;''&lt;/span&gt;&lt;span class="s1"&gt;; DROP TABLE _values; --'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The whole payload stays a single string literal. The tail of the payload never escapes the quotes, so it never gets executed.&lt;/p&gt;

&lt;p&gt;For numerics the builder doesn't even concatenate raw user text — it first parses it with &lt;code&gt;TRY_CAST&lt;/code&gt; and only inlines the parsed value if the parse succeeded. From &lt;a href="https://github.com/redbase-app/redb/blob/main/redb.MSSql/sql/v2-pvt/06_pvt_hierarchical.sql" rel="noopener noreferrer"&gt;&lt;code&gt;06_pvt_hierarchical.sql&lt;/code&gt;&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;DECLARE&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;ha_id&lt;/span&gt; &lt;span class="nb"&gt;BIGINT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;TRY_CAST&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;ha_raw&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="nb"&gt;BIGINT&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;IF&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;ha_id&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;
    &lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="k"&gt;result&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;N&lt;/span&gt;&lt;span class="s1"&gt;' AND dbo.pvt_is_descendant_of('&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="k"&gt;alias&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;N&lt;/span&gt;&lt;span class="s1"&gt;'.[_id], '&lt;/span&gt;
                 &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="k"&gt;CAST&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;ha_id&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;NVARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;N&lt;/span&gt;&lt;span class="s1"&gt;') = 1'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;-- If @ha_id is NULL the clause is silently dropped — bad input never&lt;/span&gt;
&lt;span class="c1"&gt;-- becomes SQL text. The only thing that ever gets inlined is a real BIGINT.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;TRY_CAST&lt;/code&gt; returns &lt;code&gt;NULL&lt;/code&gt; rather than raising on bad input, and the builder simply omits the clause in that case — so &lt;code&gt;"1; DROP TABLE _objects --"&lt;/code&gt; never reaches the SQL text. The behaviour is fail-closed (no clause emitted) rather than fail-loud (no exception thrown); the predicate effectively becomes a no-op. Same pattern for &lt;code&gt;WhereLevel&lt;/code&gt; (&lt;code&gt;TRY_CAST AS INT&lt;/code&gt;), &lt;code&gt;WhereHasDescendant&lt;/code&gt;, &lt;code&gt;WhereContainsObject&lt;/code&gt;, and the other base-id predicates. For dates it's &lt;code&gt;TRY_CONVERT(datetimeoffset, …)&lt;/code&gt;, for GUIDs it's &lt;code&gt;TRY_CONVERT(uniqueidentifier, …)&lt;/code&gt; — quote-then-typed-parse, drop-on-fail.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Identifiers come from &lt;code&gt;_structures&lt;/code&gt; via &lt;code&gt;QUOTENAME()&lt;/code&gt;, never from raw JSON.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Every pivot column alias, every projection alias, every table/column name in the dynamic SQL goes through &lt;code&gt;QUOTENAME()&lt;/code&gt;. From the pivot CTE builder:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;DECLARE&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="k"&gt;alias&lt;/span&gt; &lt;span class="n"&gt;NVARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;420&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;QUOTENAME&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;field_name&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;  &lt;span class="c1"&gt;-- @field_name comes from _structures&lt;/span&gt;
&lt;span class="c1"&gt;-- ...&lt;/span&gt;
&lt;span class="k"&gt;RETURN&lt;/span&gt; &lt;span class="n"&gt;N&lt;/span&gt;&lt;span class="s1"&gt;'o.'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;QUOTENAME&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;col&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;N&lt;/span&gt;&lt;span class="s1"&gt;' AS '&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="k"&gt;alias&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;QUOTENAME('weird]name')&lt;/code&gt; returns &lt;code&gt;[weird]]name]&lt;/code&gt; — embedded &lt;code&gt;]&lt;/code&gt; is doubled, the result is always a single safely-bracketed identifier. As on the PG side, the &lt;em&gt;source&lt;/em&gt; of &lt;code&gt;@field_name&lt;/code&gt; is a &lt;code&gt;_structures._name&lt;/code&gt; row matched by &lt;code&gt;(_id_scheme = @scheme AND _name = @json_key)&lt;/code&gt; — if the JSON refers to a field the queried scheme doesn't have, the lookup returns nothing and the field is rejected with a &lt;code&gt;RAISERROR&lt;/code&gt; before any SQL text is built.&lt;/p&gt;

&lt;p&gt;For base-fields (&lt;code&gt;0$:id&lt;/code&gt;, &lt;code&gt;0$:parent_id&lt;/code&gt;, &lt;code&gt;0$:scheme_id&lt;/code&gt;, …) there's a fixed CASE; unknown bases &lt;code&gt;RAISERROR&lt;/code&gt; with the same "Unknown RedbObject base field" message as the PG side.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Operator names are a fixed CASE/IF whitelist with &lt;code&gt;RAISERROR&lt;/code&gt; on miss.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Same logic as PG: &lt;code&gt;$eq&lt;/code&gt;, &lt;code&gt;$gt&lt;/code&gt;, &lt;code&gt;$lt&lt;/code&gt;, &lt;code&gt;$in&lt;/code&gt;, &lt;code&gt;$like&lt;/code&gt;, &lt;code&gt;$arrayContains&lt;/code&gt;, &lt;code&gt;$expr&lt;/code&gt;, etc. are matched by a CASE tree. Anything not on the list raises:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="n"&gt;RAISERROR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;N&lt;/span&gt;&lt;span class="s1"&gt;'pvt: unsupported operator "%s"'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;op&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;Sql.Function&lt;/code&gt; calls go through the same whitelist — &lt;code&gt;COALESCE&lt;/code&gt;, &lt;code&gt;LOWER&lt;/code&gt;, &lt;code&gt;UPPER&lt;/code&gt;, &lt;code&gt;LEN&lt;/code&gt;, &lt;code&gt;LEFT&lt;/code&gt;, &lt;code&gt;RIGHT&lt;/code&gt;, &lt;code&gt;ISNULL&lt;/code&gt;, etc. Unknown function names raise; the user can't smuggle &lt;code&gt;xp_cmdshell&lt;/code&gt; through &lt;code&gt;Sql.Function&amp;lt;int&amp;gt;("xp_cmdshell", "...")&lt;/code&gt; because that name isn't in the CASE.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. The dynamic SQL is built only from the three pieces above and executed with &lt;code&gt;sp_executesql&lt;/code&gt;.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Final execution is &lt;code&gt;EXEC sp_executesql @sql&lt;/code&gt; (with parameterized &lt;code&gt;@scheme_id&lt;/code&gt;, &lt;code&gt;@limit&lt;/code&gt;, &lt;code&gt;@offset&lt;/code&gt; etc. as &lt;code&gt;sp_executesql&lt;/code&gt; arguments, not concatenated). Every byte of &lt;code&gt;@sql&lt;/code&gt; came from either:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a &lt;code&gt;REPLACE&lt;/code&gt;-quoted string literal in &lt;code&gt;N'…'&lt;/code&gt;, or&lt;/li&gt;
&lt;li&gt;a &lt;code&gt;TRY_CAST&lt;/code&gt;-validated typed value inlined as its parsed form, or&lt;/li&gt;
&lt;li&gt;a &lt;code&gt;QUOTENAME&lt;/code&gt;-bracketed identifier sourced from &lt;code&gt;_structures&lt;/code&gt; by id lookup, or&lt;/li&gt;
&lt;li&gt;a hardcoded operator template chosen by a whitelist CASE.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The &lt;code&gt;99_smoke_auto.sql&lt;/code&gt; regression suite on MSSql includes the same adversarial payloads as the PG suite — &lt;code&gt;'; DROP TABLE …&lt;/code&gt;, embedded quotes, &lt;code&gt;]&lt;/code&gt; in field names, type-mismatched literals, unknown operators — and expects each one to be either rejected with &lt;code&gt;RAISERROR&lt;/code&gt; (operators / unknown bases / unknown identifiers), silently dropped from the predicate (typed-value parse failures), or come out the other side as an inert quoted literal that does nothing (string values).&lt;/p&gt;

&lt;h3&gt;
  
  
  Summary
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Layer&lt;/th&gt;
&lt;th&gt;PG Free emits via&lt;/th&gt;
&lt;th&gt;MSSql Free emits via&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;String values&lt;/td&gt;
&lt;td&gt;&lt;code&gt;format('… %L', val)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;N'' + REPLACE(val, '''', '''''') + N''&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Numeric / date / GUID values&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;%L::bigint&lt;/code&gt; / &lt;code&gt;%L::numeric&lt;/code&gt; / &lt;code&gt;%L::timestamptz&lt;/code&gt; / &lt;code&gt;%L::uuid&lt;/code&gt; (parse-or-die at runtime)&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;TRY_CAST(... AS BIGINT)&lt;/code&gt; / &lt;code&gt;TRY_CONVERT(datetimeoffset, ...)&lt;/code&gt; / &lt;code&gt;TRY_CONVERT(uniqueidentifier, ...)&lt;/code&gt; (parse-or-drop-clause before inline)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Identifiers&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;%I&lt;/code&gt; / &lt;code&gt;quote_ident(name_from__structures)&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;&lt;code&gt;QUOTENAME(name_from__structures)&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Operators / function names&lt;/td&gt;
&lt;td&gt;CASE whitelist → &lt;code&gt;RAISE EXCEPTION&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;CASE whitelist → &lt;code&gt;RAISERROR&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Final execution&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;EXECUTE&lt;/code&gt; of the assembled string&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;EXEC sp_executesql&lt;/code&gt; of the assembled string&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Same three independent gates on both databases. Any one of them is sufficient to defeat injection; the suite tests all three.&lt;/p&gt;

&lt;p&gt;If you find a leak in either dialect, file an issue with the JSON payload that triggers it. I'll treat it as a security bug, not a feature request.&lt;/p&gt;




&lt;h2&gt;
  
  
  The polyglot facet API — Free's one structural advantage
&lt;/h2&gt;

&lt;p&gt;A side-effect of Free's SQL-side builder: it's accessible from any language. Pro compiles in C#, which is its strength but also its limitation — you need a .NET process. Free compiles in SQL, so the same builder C# Free clients hit is callable from any driver that can run SQL.&lt;/p&gt;

&lt;p&gt;The shipped surface is two functions, not one:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Function&lt;/th&gt;
&lt;th&gt;Input&lt;/th&gt;
&lt;th&gt;Output&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;pvt_build_query_sql(scheme_id, facets jsonb, limit, offset, order jsonb, max_depth, …)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;facets&lt;/td&gt;
&lt;td&gt;the assembled &lt;code&gt;SELECT _id FROM …&lt;/code&gt; as &lt;code&gt;text&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;get_objects_json(ids bigint[], max_depth integer DEFAULT 10)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;matched ids&lt;/td&gt;
&lt;td&gt;table of &lt;code&gt;(Id bigint, JsonData text)&lt;/code&gt; with fully hydrated objects&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The honest flow is therefore two SQL roundtrips: build → &lt;code&gt;EXECUTE&lt;/code&gt; the returned text → hydrate. The 10-line plpgsql wrapper below collapses that into a single call you can ship into your DB once and then call from any language:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- one-time setup: paste into your DB as a wrapper of the two shipped functions&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;OR&lt;/span&gt; &lt;span class="k"&gt;REPLACE&lt;/span&gt; &lt;span class="k"&gt;FUNCTION&lt;/span&gt; &lt;span class="n"&gt;redb_run_facets&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;p_scheme_id&lt;/span&gt; &lt;span class="nb"&gt;bigint&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;p_facets&lt;/span&gt;    &lt;span class="n"&gt;jsonb&lt;/span&gt;   &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;p_limit&lt;/span&gt;     &lt;span class="nb"&gt;integer&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;p_offset&lt;/span&gt;    &lt;span class="nb"&gt;integer&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;p_order&lt;/span&gt;     &lt;span class="n"&gt;jsonb&lt;/span&gt;   &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;p_max_depth&lt;/span&gt; &lt;span class="nb"&gt;integer&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;RETURNS&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;"Id"&lt;/span&gt; &lt;span class="nb"&gt;bigint&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;"JsonData"&lt;/span&gt; &lt;span class="nb"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;LANGUAGE&lt;/span&gt; &lt;span class="n"&gt;plpgsql&lt;/span&gt; &lt;span class="k"&gt;STABLE&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="err"&gt;$$&lt;/span&gt;
&lt;span class="k"&gt;DECLARE&lt;/span&gt; &lt;span class="n"&gt;v_sql&lt;/span&gt; &lt;span class="nb"&gt;text&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;v_ids&lt;/span&gt; &lt;span class="nb"&gt;bigint&lt;/span&gt;&lt;span class="p"&gt;[];&lt;/span&gt;
&lt;span class="k"&gt;BEGIN&lt;/span&gt;
    &lt;span class="n"&gt;v_sql&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pvt_build_query_sql&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p_scheme_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;p_facets&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;p_limit&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;p_offset&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;p_order&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;p_max_depth&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;EXECUTE&lt;/span&gt; &lt;span class="s1"&gt;'SELECT array_agg(_id) FROM ('&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;v_sql&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="s1"&gt;') t'&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="n"&gt;v_ids&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;IF&lt;/span&gt; &lt;span class="n"&gt;v_ids&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;THEN&lt;/span&gt; &lt;span class="k"&gt;RETURN&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;END&lt;/span&gt; &lt;span class="n"&gt;IF&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;RETURN&lt;/span&gt; &lt;span class="n"&gt;QUERY&lt;/span&gt; &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;g&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"Id"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;g&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"JsonData"&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;get_objects_json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v_ids&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;p_max_depth&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;g&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;END&lt;/span&gt; &lt;span class="err"&gt;$$&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With that wrapper in place:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Python — psycopg
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;psycopg&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;

&lt;span class="n"&gt;facets&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;$and&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Age&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;  &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;$gt&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;}},&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;City&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;$eq&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;London&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}},&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;psycopg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;DSN&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cursor&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;cur&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;cur&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;SELECT &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;JsonData&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt; FROM redb_run_facets(%s, %s::jsonb, %s, %s)&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;scheme_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;facets&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;    &lt;span class="c1"&gt;# limit, offset
&lt;/span&gt;    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;employees&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;cur&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fetchall&lt;/span&gt;&lt;span class="p"&gt;()]&lt;/span&gt;
    &lt;span class="c1"&gt;# each row is JSON text — nested objects, arrays, dictionaries,
&lt;/span&gt;    &lt;span class="c1"&gt;# ListItem references — all fully reconstructed.
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Node — pg&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;facets&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;$and&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="na"&gt;Age&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;$gt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;City&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;$eq&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;London&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;}]&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;rows&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;SELECT "JsonData" FROM redb_run_facets($1, $2::jsonb, $3, $4)&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;schemeId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;facets&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;employees&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;JsonData&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// Go — pgx&lt;/span&gt;
&lt;span class="n"&gt;facets&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;`{"$and":[{"Age":{"$gt":30}},{"City":{"$eq":"London"}}]}`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s"&gt;`SELECT "JsonData" FROM redb_run_facets($1, $2::jsonb, $3, $4)`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;schemeID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;facets&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c"&gt;// scan each row into a json.RawMessage&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Java — JDBC&lt;/span&gt;
&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;facets&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"{\"$and\":[{\"Age\":{\"$gt\":30}},{\"City\":{\"$eq\":\"London\"}}]}"&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;PreparedStatement&lt;/span&gt; &lt;span class="n"&gt;ps&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;prepareStatement&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
        &lt;span class="s"&gt;"SELECT \"JsonData\" FROM redb_run_facets(?, ?::jsonb, ?, ?)"&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;ps&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setLong&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;schemeId&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;ps&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setString&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;facets&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;ps&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setInt&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;ps&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setInt&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ResultSet&lt;/span&gt; &lt;span class="n"&gt;rs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ps&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;executeQuery&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;next&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;hydratedJson&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;rs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getString&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- raw psql — same engine, no driver at all&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="nv"&gt;"JsonData"&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;redb_run_facets&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="mi"&gt;42&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nb"&gt;bigint&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;'{"$and":[{"Age":{"$gt":30}},{"City":{"$eq":"London"}}]}'&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;jsonb&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you'd rather skip the wrapper and call the shipped functions directly, the two-roundtrip pattern is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- 1. build the query text&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;pvt_build_query_sql&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;42&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'{"$and":[…]}'&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;jsonb&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="k"&gt;sql&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;-- 2. EXECUTE the returned text on the client → collect _id values&lt;/span&gt;
&lt;span class="c1"&gt;-- 3. hydrate&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;get_objects_json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ARRAY&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;101&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;102&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;103&lt;/span&gt;&lt;span class="p"&gt;]::&lt;/span&gt;&lt;span class="nb"&gt;bigint&lt;/span&gt;&lt;span class="p"&gt;[],&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The JSON facet schema is a stable contract — see &lt;a href="https://github.com/redbase-app/redb/blob/main/docs/FreePvtQuery/FREE-OVER-PRO.md" rel="noopener noreferrer"&gt;&lt;code&gt;FREE-OVER-PRO.md&lt;/code&gt;&lt;/a&gt; for the full operator list. Tuple-key dictionaries, nested &lt;code&gt;RedbObject&amp;lt;T&amp;gt;&lt;/code&gt; references, &lt;code&gt;RedbListItem&lt;/code&gt; references with &lt;code&gt;.Id&lt;/code&gt;/&lt;code&gt;.Value&lt;/code&gt;/&lt;code&gt;.Alias&lt;/code&gt; — all reconstruct correctly in the JSON &lt;code&gt;get_objects_json&lt;/code&gt; emits. No schema knowledge needed on the client side.&lt;/p&gt;

&lt;p&gt;And the injection story above applies the same way here — your &lt;code&gt;psycopg&lt;/code&gt;/&lt;code&gt;pgx&lt;/code&gt;/&lt;code&gt;JDBC&lt;/code&gt; driver binds the JSON as a &lt;code&gt;jsonb&lt;/code&gt; parameter, so even your facet &lt;em&gt;payload&lt;/em&gt; doesn't touch a SQL parser until it's already inside the parameterized builder.&lt;/p&gt;

&lt;p&gt;This is Free-only by design. Pro's edge has always been the CLR-side compiled pipeline plus parallel materialization (&lt;code&gt;ProLazyPropsLoader&lt;/code&gt; does 2 bulk &lt;code&gt;_values&lt;/code&gt; SELECTs, indexes them into &lt;code&gt;ILookup&lt;/code&gt;, and &lt;code&gt;ProPropsMaterializer&lt;/code&gt; runs &lt;code&gt;Parallel.ForEach&lt;/code&gt; to assemble 20+ value types, nested objects, arrays, dictionaries, ListItem refs into typed &lt;code&gt;RedbObject&amp;lt;T&amp;gt;&lt;/code&gt; — all CPU-bound, zero further DB access). That stays where it is.&lt;/p&gt;




&lt;h2&gt;
  
  
  The honest matrix
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Capability&lt;/th&gt;
&lt;th&gt;Free 3.0.0&lt;/th&gt;
&lt;th&gt;Pro 3.0.0&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Query shape (PVT CTE, base pushdown, MAX/CASE ↔ array_agg)&lt;/td&gt;
&lt;td&gt;Same&lt;/td&gt;
&lt;td&gt;Same&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;GroupBy + HAVING&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;ArrayGroupBy&lt;/code&gt; (unnest / OUTER APPLY)&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;DistinctBy&lt;/code&gt;, &lt;code&gt;Sql.Function&lt;/code&gt; (whitelist), &lt;code&gt;$expr&lt;/code&gt; trees&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tree queries (&lt;code&gt;WhereHasAncestor&lt;/code&gt;, &lt;code&gt;WhereLevel&lt;/code&gt;, etc.)&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Window functions (&lt;code&gt;Lag&lt;/code&gt;/&lt;code&gt;Lead&lt;/code&gt;/&lt;code&gt;Rank&lt;/code&gt;/&lt;code&gt;DenseRank&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Polyglot facet API (Python / Node / Go / Java / raw SQL)&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Compiled in C# (no JSON intermediate)&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Parallel CLR materialization (&lt;code&gt;Parallel.ForEach&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Bulk save via &lt;code&gt;COPY&lt;/code&gt; protocol&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hash-based &lt;code&gt;ChangeTracking&lt;/code&gt; (diff only what changed)&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Combined &lt;code&gt;GroupBy&lt;/code&gt; + window + &lt;code&gt;AggregateBatch&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Two things to be honest about:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Free has higher latency on full-object load&lt;/strong&gt; than Pro — &lt;code&gt;get_object_json&lt;/code&gt; builds JSON in plpgsql and the client deserializes. Pro's bulk-+-parallel materialization is a real win when you're loading thousands of complex objects with 20+ nested fields each.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pro still has features Free won't get&lt;/strong&gt; — parallel bulk materialization (&lt;code&gt;ProLazyPropsLoader&lt;/code&gt; + &lt;code&gt;ProPropsMaterializer&lt;/code&gt;) and hash-based &lt;code&gt;ChangeTracking&lt;/code&gt; remain Pro-only. The query shape itself is now identical.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But the &lt;em&gt;query shape&lt;/em&gt; — the part that determines whether the database uses your indexes properly, whether the plan caches, whether 30 props in a WHERE becomes 30 &lt;code&gt;EXISTS&lt;/code&gt; or one pivot — that's the same in both tiers now.&lt;/p&gt;




&lt;h2&gt;
  
  
  Migration notes
&lt;/h2&gt;

&lt;p&gt;If you're on &lt;code&gt;2.0.x&lt;/code&gt; Free, upgrading to &lt;code&gt;3.0.0&lt;/code&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Update packages&lt;/strong&gt; — &lt;code&gt;RedBase.Core&lt;/code&gt;, &lt;code&gt;RedBase.Postgres&lt;/code&gt; / &lt;code&gt;RedBase.MSSql&lt;/code&gt; to &lt;code&gt;3.0.0&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;First startup applies the v2-pvt bundle automatically.&lt;/strong&gt; The new module is additive at the C# API level — your existing LINQ-style queries just compile through the new path. Note: the legacy plpgsql function &lt;code&gt;search_objects_with_facets()&lt;/code&gt; (used only by pre-2.0 raw-SQL callers) is &lt;strong&gt;no longer installed by &lt;code&gt;redb_init.sql&lt;/code&gt;&lt;/strong&gt; — it lives under &lt;code&gt;redb.Postgres/sql/deprecated/&lt;/code&gt; if you still need it; paste it manually before upgrading if you had direct SQL callers.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pre-existing config flag is now honored.&lt;/strong&gt; &lt;code&gt;DefaultStrictDeleteExtra&lt;/code&gt; in built-in presets &lt;code&gt;Development&lt;/code&gt;, &lt;code&gt;HighPerformance&lt;/code&gt;, &lt;code&gt;Migration&lt;/code&gt; was set to &lt;code&gt;false&lt;/code&gt; but silently ignored before 3.0.0. It now actually works — meaning those presets will no longer auto-delete &lt;code&gt;_structures&lt;/code&gt; rows for fields removed from your &lt;code&gt;Props&lt;/code&gt; class on startup. If you were relying on the old (silently-broken) behavior, set &lt;code&gt;c.DefaultStrictDeleteExtra = true&lt;/code&gt; explicitly.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;MSSql Free users&lt;/strong&gt;: the wide-CASE inline pivot is gone. If you had query-text-matching log scrapers, the new shape is &lt;code&gt;WITH pvt_cte AS (... MAX(CASE WHEN _id_structure = @p1 AND _array_index IS NULL THEN _X END) ...) SELECT o.* FROM _objects o JOIN pvt_cte pvt ON ...&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;No source-level breaking API changes. No data migrations. If you've never touched the SQL surface directly, you can upgrade and not notice anything except your slow queries getting faster.&lt;/p&gt;




&lt;h2&gt;
  
  
  Known gaps I'd like help finding
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Nested-field cross-scheme JOIN&lt;/strong&gt; — &lt;code&gt;Where(o =&amp;gt; o.CurrentProject.Props.Name == "X")&lt;/code&gt; where &lt;code&gt;CurrentProject&lt;/code&gt; is a &lt;code&gt;RedbObject&amp;lt;OtherScheme&amp;gt;&lt;/code&gt; reference. Free PVT rejects the path; Pro &lt;code&gt;SchemeFieldResolver&lt;/code&gt; throws. Needs new infrastructure on both sides. Tracked in &lt;a href="https://github.com/redbase-app/redb/blob/main/docs/FreePvtQuery/FREE-OVER-PRO.md" rel="noopener noreferrer"&gt;&lt;code&gt;FREE-OVER-PRO.md §2 #6b&lt;/code&gt;&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;MSSql Free is fresh.&lt;/strong&gt; Two weeks of intensive testing, not two years. &lt;code&gt;EXPLAIN&lt;/code&gt; traces and failing repros are very welcome.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Polyglot facet API documentation&lt;/strong&gt; is thinner than the C# API. The operator surface is stable (it's what the C# parser emits), but it's not yet a documented wire-protocol RFC.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There will be follow-up fixes after this post lands — that's expected. If you hit something, please open a thread in &lt;a href="https://github.com/redbase-app/redb/discussions" rel="noopener noreferrer"&gt;GitHub Discussions&lt;/a&gt; rather than sitting on it.&lt;/p&gt;




&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Free&lt;/span&gt;
dotnet add package RedBase.Core      &lt;span class="nt"&gt;--version&lt;/span&gt; 3.0.0
dotnet add package RedBase.Postgres  &lt;span class="nt"&gt;--version&lt;/span&gt; 3.0.0     &lt;span class="c"&gt;# or RedBase.MSSql&lt;/span&gt;

&lt;span class="c"&gt;# Pro (commercial)&lt;/span&gt;
dotnet add package RedBase.Core.Pro       &lt;span class="nt"&gt;--version&lt;/span&gt; 3.0.0
dotnet add package RedBase.Postgres.Pro   &lt;span class="nt"&gt;--version&lt;/span&gt; 3.0.0   &lt;span class="c"&gt;# or RedBase.MSSql.Pro&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Repo: &lt;a href="https://github.com/redbase-app/redb" rel="noopener noreferrer"&gt;https://github.com/redbase-app/redb&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Full 3.0.0 changelog: &lt;a href="https://github.com/redbase-app/redb/blob/main/CHANGELOG.md" rel="noopener noreferrer"&gt;CHANGELOG.md&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Architecture deep-dive (with the same LINQ→SQL examples): &lt;a href="https://redbase.app/architecture" rel="noopener noreferrer"&gt;https://redbase.app/architecture&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Issues / bug reports: &lt;a href="https://github.com/redbase-app/redb/issues" rel="noopener noreferrer"&gt;https://github.com/redbase-app/redb/issues&lt;/a&gt; — please cite the SQL and the dialect.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you read all the way to here, thank you. Tell me what's wrong with it.&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>csharp</category>
      <category>postgres</category>
      <category>opensource</category>
    </item>
    <item>
      <title>From Closed Internal Stack to Open-Source Ecosystem: I Finally Shipped Three Years of .NET Infrastructure</title>
      <dc:creator>rinat kozin</dc:creator>
      <pubDate>Sat, 23 May 2026 16:07:54 +0000</pubDate>
      <link>https://dev.to/rinat_kozin_d0a2ef43e7824/from-closed-internal-stack-to-open-source-ecosystem-i-finally-shipped-three-years-of-net-4lcf</link>
      <guid>https://dev.to/rinat_kozin_d0a2ef43e7824/from-closed-internal-stack-to-open-source-ecosystem-i-finally-shipped-three-years-of-net-4lcf</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the&lt;br&gt;
&lt;a href="https://dev.to/challenges/github-2026-05-21"&gt;GitHub Finish-Up-A-Thon Challenge&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Why this exists (the actual pain)
&lt;/h2&gt;

&lt;p&gt;Before the architecture talk, the &lt;em&gt;why&lt;/em&gt;. Three years ago&lt;br&gt;
I watched my team — and every other .NET team I knew —&lt;br&gt;
burn most of their hours not on business logic but on&lt;br&gt;
plumbing the same four layers over and over:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;EF Core + migrations.&lt;/strong&gt; &lt;code&gt;DbContext&lt;/code&gt;, fluent mappings,
navigation properties, N+1 puzzles, lazy/eager loading,
a migration per field, separate read/write models if you
go CQRS. On a non-trivial object graph that's hundreds
of hours just for the data layer, before any business
logic is written.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A pile of integration connectors.&lt;/strong&gt; Kafka here,
RabbitMQ there, S3 for files, SFTP for the legacy
partner, an HTTP webhook for the new one, each one
hand-rolled with its own retry policy, dead-letter,
idempotency, serialization, health check, telemetry.
Ten integrations × 40-80 hours each.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Auth.&lt;/strong&gt; OAuth flows, refresh tokens, scope checking,
claims transformation, multi-tenant, M2M, DPoP if you
want to be modern — built from &lt;code&gt;IdentityServer&lt;/code&gt; /
&lt;code&gt;Auth0&lt;/code&gt; / &lt;code&gt;Keycloak&lt;/code&gt; plus 200-600 hours of glue per
serious deployment.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Runtime.&lt;/strong&gt; Hot-reload without dropping in-flight
messages, graceful drain on redeploy, cluster
coordination, leader election, watchdog, dashboard —
usually missing entirely on .NET, or assembled from 5-7
unrelated NuGets that don't share conventions.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The worst part isn't any single layer — it's the &lt;strong&gt;seams&lt;br&gt;
between them&lt;/strong&gt;. Where the auth user becomes the EF entity&lt;br&gt;
becomes the Kafka message becomes the S3 blob, every seam&lt;br&gt;
is one more serialization, one more mapping, one more&lt;br&gt;
versioning headache, one more place a 3 AM page comes from.&lt;/p&gt;

&lt;p&gt;RedBase is built so that &lt;strong&gt;business code is the only thing&lt;br&gt;
left to write&lt;/strong&gt;. The class IS the schema (no EF, no&lt;br&gt;
migrations). The 22 transports share one DSL (no per-connector&lt;br&gt;
plumbing). Identity is a &lt;code&gt;direct-vm://&lt;/code&gt; Route (calling auth&lt;br&gt;
is calling a function, not a network round-trip). Tsak gives&lt;br&gt;
you hot-reload, cluster, dashboard, drain out of the box. The&lt;br&gt;
seams collapse because everything lives on the same fabric.&lt;/p&gt;

&lt;p&gt;On the one full business workflow where I had honest before/after&lt;br&gt;
numbers — built the traditional way vs built on the RedBase&lt;br&gt;
stack — the human effort was &lt;strong&gt;roughly ~3,000 hours vs ~128&lt;br&gt;
hours&lt;/strong&gt;. That ratio isn't magic on any single layer (where&lt;br&gt;
each subsystem gives 3-5× at most); it comes from the seams&lt;br&gt;
no longer existing. When I asked Claude Opus 4.7 to&lt;br&gt;
sanity-check that estimate against typical .NET project&lt;br&gt;
breakdowns (data layer + integrations + auth + runtime +&lt;br&gt;
inter-seam testing), the order of magnitude held up.&lt;/p&gt;

&lt;p&gt;One stack, one team, one architectural style — so the team&lt;br&gt;
gets to write features instead of wiring infrastructure.&lt;br&gt;
&lt;strong&gt;That&lt;/strong&gt; is the project. Everything below is just how I got&lt;br&gt;
there.&lt;/p&gt;
&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;RedBase&lt;/strong&gt; — a four-pillar open-source ecosystem for .NET&lt;br&gt;
that grew up inside one production system over three years&lt;br&gt;
and finally got shipped to the world this spring. Three&lt;br&gt;
pillars are now public on GitHub under Apache 2.0, and the&lt;br&gt;
fourth is in pre-release polish:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. &lt;a href="https://github.com/redbase-app/redb" rel="noopener noreferrer"&gt;redb&lt;/a&gt;&lt;/strong&gt; — typed&lt;br&gt;
object storage engine. Your C# class IS the schema. Two&lt;br&gt;
physical tables (&lt;code&gt;_objects&lt;/code&gt; + &lt;code&gt;_values&lt;/code&gt;), full LINQ, zero&lt;br&gt;
migrations, recursive-CTE tree queries, bulk &lt;code&gt;COPY BINARY&lt;/code&gt;&lt;br&gt;
saves on PostgreSQL and &lt;code&gt;SqlBulkCopy&lt;/code&gt; on SQL Server. Free&lt;br&gt;
core + Pro tier with tree-diff ChangeTracking saves.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. &lt;a href="https://github.com/redbase-app/redb-route" rel="noopener noreferrer"&gt;redb-route&lt;/a&gt;&lt;/strong&gt;&lt;br&gt;
— Apache Camel for .NET. Fluent C# DSL for&lt;br&gt;
&lt;code&gt;From → Process → To&lt;/code&gt; pipelines, &lt;strong&gt;22 transport packages&lt;/strong&gt;&lt;br&gt;
(Kafka, RabbitMQ, MQTT, S3, gRPC, SFTP, AMQP, Azure&lt;br&gt;
Service Bus, IBM MQ, Elasticsearch, Redis, LDAP, FTP, HTTP,&lt;br&gt;
WebSocket, SignalR, Firebase, TCP, Mail, SQL, Quartz,&lt;br&gt;
generic File), &lt;strong&gt;80+ EIP patterns&lt;/strong&gt; (Splitter, Aggregator,&lt;br&gt;
CBR, Circuit Breaker, Saga, ...), compiled expression&lt;br&gt;
engine, transactional routes, OpenTelemetry built-in.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. &lt;a href="https://github.com/redbase-app/redb-tsak" rel="noopener noreferrer"&gt;redb-tsak&lt;/a&gt;&lt;/strong&gt;&lt;br&gt;
— the .NET analogue of Apache Karaf / Camel K. Production&lt;br&gt;
runtime container: drop a &lt;code&gt;.tpkg&lt;/code&gt; bundle (ZIP with&lt;br&gt;
&lt;code&gt;manifest.json&lt;/code&gt; + entry-point DLLs + dependency DLLs +&lt;br&gt;
per-module JSON config) — or just a bare &lt;code&gt;.dll&lt;/code&gt; for the&lt;br&gt;
simplest cases — into &lt;code&gt;Libs/&lt;/code&gt;, get hot-reload without&lt;br&gt;
dropping in-flight messages, REST + Blazor dashboard,&lt;br&gt;
watchdog, Quartz scheduler, three deployment modes&lt;br&gt;
(Standalone / Single-node+redb / Cluster with leader&lt;br&gt;
election &amp;amp; auto-rebalance), per-module&lt;br&gt;
&lt;code&gt;AssemblyLoadContext&lt;/code&gt; isolation, API Key + HMAC-SHA256&lt;br&gt;
security, 30-command CLI, typed C# client. &lt;strong&gt;415+ tests.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. redb.Identity&lt;/strong&gt; &lt;em&gt;(pre-release, repo opening soon)&lt;/em&gt; —&lt;br&gt;
OAuth 2.1 / OIDC server, architecturally transport-agnostic:&lt;br&gt;
every endpoint is a &lt;code&gt;direct-vm://&lt;/code&gt; Route. That gives two&lt;br&gt;
ready usage modes today: &lt;strong&gt;(a) in-process&lt;/strong&gt; — call the&lt;br&gt;
auth server directly from another module in the same&lt;br&gt;
run-time with zero network hop, zero serialization, zero&lt;br&gt;
HTTP stack (this is what &lt;code&gt;direct-vm://&lt;/code&gt; &lt;em&gt;is&lt;/em&gt;), and&lt;br&gt;
&lt;strong&gt;(b) HTTP&lt;/strong&gt; — a full OAuth 2.1 / OIDC HTTP facade is&lt;br&gt;
shipped and working. The other RPC-capable facades —&lt;br&gt;
gRPC, RabbitMQ RPC, AMQP request/reply, IBM MQ&lt;br&gt;
request/reply, WebSocket, SignalR, TCP — are on the&lt;br&gt;
roadmap and become near-trivial to add because the core&lt;br&gt;
logic is already wire-format independent. (The&lt;br&gt;
fire-and-forget transports in the Route set — Kafka&lt;br&gt;
producer, File, S3, Mail, etc. — don't fit an auth&lt;br&gt;
server, so they're correctly out of scope.) OpenIddict&lt;br&gt;
under the hood, REDB-backed storage, DPoP / PAR / Dynamic&lt;br&gt;
Client Registration / SCIM 2.0 / FIDO2 / WebAuthn /&lt;br&gt;
RFC 8417 shared-signals support.&lt;br&gt;
&lt;strong&gt;1751+ tests&lt;/strong&gt; passing before public release.&lt;/p&gt;

&lt;p&gt;Total scope: &lt;strong&gt;~385k LOC&lt;/strong&gt; (326k C# + 58k SQL) across&lt;br&gt;
~2200 source files, &lt;strong&gt;Apache 2.0&lt;/strong&gt;, NuGet-published, all&lt;br&gt;
docs at &lt;a href="https://redbase.app/why-redbase" rel="noopener noreferrer"&gt;redbase.app/why-redbase&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Live docs and positioning:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://redbase.app/why-redbase" rel="noopener noreferrer"&gt;redbase.app/why-redbase&lt;/a&gt;
— full "Sound familiar?" pain-point walkthrough with
side-by-side Traditional-vs-RedBase code examples&lt;/li&gt;
&lt;li&gt;The docs site itself is powered by RedBase — every page,
example, and API reference is a REDB object in MSSQL. We
eat our own cooking.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Published article series on dev.to&lt;/strong&gt;&lt;br&gt;
(&lt;a href="https://dev.to/rinat_kozin_d0a2ef43e7824"&gt;author page&lt;/a&gt;) —&lt;br&gt;
the ecosystem is too large for one post, so I'm publishing&lt;br&gt;
a deep-dive series, one pillar at a time:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;em&gt;May 13&lt;/em&gt; — &lt;a href="https://dev.to/rinat_kozin_d0a2ef43e7824/we-built-an-enterprise-integration-stack-for-net-from-scratch-eav-dsl-runtime-2l16"&gt;We built an enterprise integration stack
for .NET from scratch: EAV + DSL +
runtime&lt;/a&gt;
— overview of the whole stack&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;May 14&lt;/em&gt; — &lt;a href="https://dev.to/rinat_kozin_d0a2ef43e7824/i-spent-a-year-building-apache-camel-for-net-heres-the-honest-state-of-it-150e"&gt;I spent a year building Apache Camel for
.NET. Here's the honest state of
it.&lt;/a&gt;
— discussion piece, what's done / what isn't&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;May 17&lt;/em&gt; — &lt;a href="https://dev.to/rinat_kozin_d0a2ef43e7824/redbroute-apache-camel-for-net-22-transports-30-eip-patterns-compiled-dsl-11m0"&gt;redb.Route — Apache Camel for .NET:
22 transports, 30+ EIP patterns, compiled
DSL&lt;/a&gt;
— integration framework deep dive&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;May 21&lt;/em&gt; — &lt;a href="https://dev.to/rinat_kozin_d0a2ef43e7824/two-tables-zero-migrations-full-linq-a-net-data-engine-thats-been-running-our-production-for-2482"&gt;An EF Core alternative for .NET apps with
complex object graphs — full LINQ, no migrations, no
DbContext&lt;/a&gt;
— REDB storage engine deep dive (this is the one
that already triggered the multi-version-deploy thread
referenced below)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;…and the series continues. Tsak (runtime container,&lt;br&gt;
hot-reload, cluster, watchdog) gets its own post next,&lt;br&gt;
then Identity (transport-agnostic OAuth 2.1 / OIDC) when&lt;br&gt;
the repo opens, then a deep dive on the tree-diff&lt;br&gt;
ChangeTracking path, then a multi-DB benchmark post.&lt;br&gt;
A stack this size is a genuinely hard engineering&lt;br&gt;
problem to explain — trying to cram it into one article&lt;br&gt;
would lie about the depth. So I'm taking it one layer&lt;br&gt;
at a time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The 30-second flavor:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// 1. Define schema — no migration needed&lt;/span&gt;
&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;RedbScheme&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"order"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;OrderProps&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;Customer&lt;/span&gt; &lt;span class="n"&gt;Customer&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;Address&lt;/span&gt; &lt;span class="n"&gt;ShippingAddress&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;OrderLineProps&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Lines&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;Dictionary&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;RedbObject&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;CouponProps&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Coupons&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// 2. Save entire object graph in one call&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;redb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SaveAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// 3. Query with full LINQ over nested props&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;hot&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;redb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Query&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;OrderProps&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;o&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Customer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Address&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;City&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="s"&gt;"London"&lt;/span&gt;
             &lt;span class="p"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Lines&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Any&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;l&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Qty&lt;/span&gt; &lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;OrderByDescending&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;o&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DateModify&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Take&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;50&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToListAsync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="c1"&gt;// 4. Tree query with recursive CTE under the hood&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;allProducts&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;redb&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TreeQuery&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Product&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="n"&gt;londonHQ&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;maxDepth&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;InStock&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToListAsync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Same dev defines a routing context with redb.Route.&lt;/span&gt;
&lt;span class="c1"&gt;// Real production patterns: REDB isn't a transport URI —&lt;/span&gt;
&lt;span class="c1"&gt;// it's accessed inside processors via DI (.ProcessWithRedb).&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;OrdersRouteBuilder&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;RouteBuilderBase&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Build&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;From&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"kafka://orders-raw"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Unmarshal&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;OrderProps&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;()&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;o&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Lines&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;EipParallel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Yes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ProcessWithRedb&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;redb&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;exchange&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;OrderLineProps&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;exchange&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;In&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Body&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;redb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SaveAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;   &lt;span class="c1"&gt;// REDB store&lt;/span&gt;
            &lt;span class="p"&gt;})&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Aggregate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;by&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;In&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Headers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"OrderId"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
                       &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;5&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Seconds&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;To&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"rabbitmq://orders-enriched"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="c1"&gt;// Staged queue with backpressure — exactly how the&lt;/span&gt;
        &lt;span class="c1"&gt;// production garage-sync route does it:&lt;/span&gt;
        &lt;span class="nf"&gt;From&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"seda://orders?concurrentConsumers=4"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ProcessWithRedb&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;EnrichFromInventoryAsync&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;To&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"http://partner-api/orders"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Pack as a .tpkg bundle (manifest.json + entry-point DLL&lt;/span&gt;
&lt;span class="c1"&gt;// + dependency DLLs + module config.json) — or for trivial&lt;/span&gt;
&lt;span class="c1"&gt;// modules, just drop the bare .dll — into Libs/ of a Tsak&lt;/span&gt;
&lt;span class="c1"&gt;// node. Tsak picks it up, runs it across the cluster,&lt;/span&gt;
&lt;span class="c1"&gt;// rebalances on node failure, drains old versions on hot&lt;/span&gt;
&lt;span class="c1"&gt;// redeploy.&lt;/span&gt;
&lt;span class="c1"&gt;//&lt;/span&gt;
&lt;span class="c1"&gt;// Need the same flow gated by an OAuth scope?&lt;/span&gt;
&lt;span class="c1"&gt;// Two ready modes today:&lt;/span&gt;
&lt;span class="c1"&gt;//   * in-process — the call goes through direct-vm://&lt;/span&gt;
&lt;span class="c1"&gt;//     straight into redb.Identity, no network, no HTTP;&lt;/span&gt;
&lt;span class="c1"&gt;//   * over HTTP — standard OAuth 2.1 / OIDC endpoints.&lt;/span&gt;
&lt;span class="c1"&gt;// Tomorrow, the same auth server can also be exposed over&lt;/span&gt;
&lt;span class="c1"&gt;// any other RPC-capable Route transport (gRPC, RabbitMQ&lt;/span&gt;
&lt;span class="c1"&gt;// RPC, AMQP request/reply, IBM MQ, WebSocket, SignalR,&lt;/span&gt;
&lt;span class="c1"&gt;// TCP, ...) as those facades land — no change to the&lt;/span&gt;
&lt;span class="c1"&gt;// auth server itself:&lt;/span&gt;
&lt;span class="c1"&gt;//&lt;/span&gt;
&lt;span class="c1"&gt;//     .Process(ctx =&amp;gt; ctx.RequireScope("orders.write"))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That last paragraph — "drops a &lt;code&gt;.tpkg&lt;/code&gt; (or just a &lt;code&gt;.dll&lt;/code&gt;),&lt;br&gt;
gets hot-reload + cluster + dashboard + REST API + drain on&lt;br&gt;
redeploy + an auth server callable from the same routing&lt;br&gt;
fabric" — is the part I'm proudest of, because it's exactly&lt;br&gt;
the piece&lt;br&gt;
that's been missing from the .NET integration story for a&lt;br&gt;
decade. Java had Karaf, Camel K, and a mature OIDC story&lt;br&gt;
glued together. .NET had nothing equivalent that wasn't&lt;br&gt;
either vendor-locked or hand-rolled per project.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Comeback Story
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Before (early 2026):&lt;/strong&gt; all four pillars existed and&lt;br&gt;
worked. Years of work. Real production load — 3-node&lt;br&gt;
cluster, ~550 internal users at a HoReCa distributor,&lt;br&gt;
~500k business objects, ~15M &lt;code&gt;_values&lt;/code&gt; rows, three months&lt;br&gt;
running every operational integration in the company.&lt;br&gt;
And yet:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;0 public repos.&lt;/strong&gt; Everything in a closed monorepo.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;0 NuGet packages.&lt;/strong&gt; Even our own internal services
built it from source.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;0 published docs.&lt;/strong&gt; A 14-line &lt;code&gt;README.md&lt;/code&gt; in each
project. "TODO: document this properly" in two of them.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;0 external users.&lt;/strong&gt; Nobody outside the company even
knew it existed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;0 stars, 0 issues, 0 community.&lt;/strong&gt; The classic
"we'll open-source it when it's ready" trap — except
it had been "ready enough for production" for over
a year and "ready to share" still felt one polish-pass
away every quarter.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The honest version: I was scared. ~385k LOC of opinionated&lt;br&gt;
architecture decisions, exposed to public criticism, with&lt;br&gt;
my real name on it. Easier to keep shipping it inside a&lt;br&gt;
contract than to put it on the internet.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;After (May 2026):&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;3 public GitHub repos&lt;/strong&gt; under
&lt;a href="https://github.com/redbase-app" rel="noopener noreferrer"&gt;github.com/redbase-app&lt;/a&gt;:
&lt;code&gt;redb&lt;/code&gt;, &lt;code&gt;redb-route&lt;/code&gt;, &lt;code&gt;redb-tsak&lt;/code&gt;. Apache 2.0.
CI, badges, real READMEs. The fourth (&lt;code&gt;redb-identity&lt;/code&gt;)
is in final polish — 1751+ tests green, repo opens
before the contest deadline.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;43 NuGet packages published&lt;/strong&gt; (&lt;code&gt;redb.Core&lt;/code&gt;,
&lt;code&gt;redb.Postgres&lt;/code&gt;, &lt;code&gt;redb.Route.Kafka&lt;/code&gt;, &lt;code&gt;redb.Tsak.CLI&lt;/code&gt;,
...) with &lt;strong&gt;~18k cumulative downloads&lt;/strong&gt;, the bulk of
that (~15k) added in the active-launch window over the
last ~10 days, on a clean upward curve day over day.
External developers can &lt;code&gt;dotnet add package&lt;/code&gt; today.
Early signal: &lt;code&gt;redb.Route&lt;/code&gt; (the integration framework)
has already overtaken &lt;code&gt;redb.Core&lt;/code&gt; in download count
— 1497 vs 1482 — meaning the "Apache Camel for .NET"
positioning is landing, not just the storage story.
Inside Route, &lt;code&gt;redb.Route.Http&lt;/code&gt; is the fastest-growing
transport — confirming the hypothesis that HTTP is the
natural first entry-point for new users exploring the
DSL.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Marketing/docs site live&lt;/strong&gt; at
&lt;a href="https://redbase.app" rel="noopener noreferrer"&gt;redbase.app&lt;/a&gt; — quick start,
architecture page, pricing, full API docs. The site
itself runs on REDB to prove the engine handles real
content load.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A 4-post dev.to series&lt;/strong&gt; already out (May 13 → May 21),
with more deep dives queued. The REDB-storage post is
already getting sharp technical questions in comments —
including one about multi-version rolling deploys that
surfaced a genuinely unsafe default in
&lt;code&gt;SyncStructuresFromTypeAsync&lt;/code&gt; (&lt;code&gt;strictDeleteExtra = true&lt;/code&gt;)
that I'd never noticed in three years of production use
because we always single-version-deploy in our internal
workflow. That defect went straight onto the fix list —
exactly the kind of feedback that justifies open-sourcing
in the first place.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;First reactions and shares from the broader .NET
community.&lt;/strong&gt; Validation that the niche is real.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The completion arc isn't "I wrote a thing." The arc is&lt;br&gt;
"I had a thing that worked for years inside one company&lt;br&gt;
and I finally let it leave the building." Different muscle&lt;br&gt;
entirely. Documentation, positioning, choosing what to call&lt;br&gt;
it (RedBase / REDB / redb — we picked one and renamed&lt;br&gt;
everything), writing READMEs for repos that had no audience&lt;br&gt;
for years, recording first impressions, drafting tweets,&lt;br&gt;
answering the first technical comment in public.&lt;/p&gt;

&lt;p&gt;That last one is the hardest part of shipping. The code&lt;br&gt;
was done. Letting it be judged was the work.&lt;/p&gt;

&lt;h2&gt;
  
  
  My Experience with GitHub Copilot
&lt;/h2&gt;

&lt;p&gt;Honest framing first: a large part of this codebase is my&lt;br&gt;
own work, written before Copilot was a serious factor.&lt;br&gt;
The architectural decisions, the storage model, the&lt;br&gt;
two-table layout, the tree-diff save strategy, the&lt;br&gt;
&lt;code&gt;direct-vm://&lt;/code&gt; transport-agnostic routing fabric that lets&lt;br&gt;
&lt;code&gt;redb.Identity&lt;/code&gt; be called in-process today with zero&lt;br&gt;
network hop &lt;em&gt;and&lt;/em&gt; exposed over HTTP today &lt;em&gt;and&lt;/em&gt; grow&lt;br&gt;
additional facades over the RPC-capable Route transports&lt;br&gt;
(gRPC, RabbitMQ RPC, AMQP, IBM MQ, WebSocket, SignalR,&lt;br&gt;
TCP, ...) tomorrow — all without touching the auth server&lt;br&gt;
itself — all of that came from years of sitting, thinking,&lt;br&gt;
prototyping, and throwing prototypes away. No AI invented&lt;br&gt;
those.&lt;/p&gt;

&lt;p&gt;What Copilot changed was the &lt;em&gt;finishing arc&lt;/em&gt;. The codebase&lt;br&gt;
had the right shape, but it also had:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;features sketched-in but not closed out;&lt;/li&gt;
&lt;li&gt;helper layers half-typed because "I'll do this properly
later";&lt;/li&gt;
&lt;li&gt;XML doc-comments missing on 80% of public surfaces;&lt;/li&gt;
&lt;li&gt;READMEs with &lt;code&gt;TODO: document this&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;corner cases I knew existed and had been deferring;&lt;/li&gt;
&lt;li&gt;six different naming conventions across four years of
rewrites that nobody had unified.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Copilot is what let me finally close all of that out&lt;br&gt;
without burning a year of evenings.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Where it was a real force multiplier:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Finishing deferred features.&lt;/strong&gt; Pieces I had on a&lt;br&gt;
mental backlog for months — round-trip serialization&lt;br&gt;
edge cases, missing operators on the LINQ provider,&lt;br&gt;
Tsak CLI commands I'd been meaning to add, missing&lt;br&gt;
transport options in &lt;code&gt;redb.Route&lt;/code&gt; — Copilot let me&lt;br&gt;
knock them out in evenings instead of weekends because&lt;br&gt;
it could read the surrounding code, infer the&lt;br&gt;
convention, and propose an implementation that I then&lt;br&gt;
reviewed and corrected. The decisions stayed mine.&lt;br&gt;
The typing speed and the "what was I about to do here"&lt;br&gt;
recovery time collapsed.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Structuring code I already had.&lt;/strong&gt; A lot of the work&lt;br&gt;
wasn't writing new logic — it was taking working but&lt;br&gt;
messy code, splitting it into the right files,&lt;br&gt;
extracting the right interfaces, renaming things&lt;br&gt;
consistently across ~2200 files. Copilot is excellent&lt;br&gt;
at that kind of mechanical-but-context-sensitive&lt;br&gt;
refactor where you need to keep semantics intact while&lt;br&gt;
moving things around.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Comments and XML documentation at scale.&lt;/strong&gt; Adding&lt;br&gt;
&lt;code&gt;/// &amp;lt;summary&amp;gt;&lt;/code&gt; to thousands of public members with&lt;br&gt;
accurate descriptions of what each method actually&lt;br&gt;
does — by hand this is a multi-week slog and you'll&lt;br&gt;
give up after two days. With Copilot reading the&lt;br&gt;
implementation and proposing the comment, then me&lt;br&gt;
correcting where it was wrong, it became a steady&lt;br&gt;
background task that actually finished.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Documentation and READMEs.&lt;/strong&gt; ~12 projects, each&lt;br&gt;
needing a consistent voice, accurate API examples,&lt;br&gt;
correct cross-references, and a quick-start that&lt;br&gt;
actually runs. With Copilot it became "draft → review&lt;br&gt;
→ correct → ship" instead of "stare at empty file →&lt;br&gt;
procrastinate." Same for the redbase.app positioning&lt;br&gt;
pages — the "Sound familiar? / WITH REDBASE" pattern&lt;br&gt;
you see on the site got drafted in one afternoon and&lt;br&gt;
then iterated.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Cross-file architectural navigation while debugging.&lt;/strong&gt;&lt;br&gt;
The &lt;code&gt;redb.Core.Pro&lt;/code&gt; ChangeTracking save path crosses&lt;br&gt;
&lt;code&gt;ValueTreeBuilder&lt;/code&gt;, &lt;code&gt;ValueTreeDiff&lt;/code&gt;,&lt;br&gt;
&lt;code&gt;ProObjectStorageProviderBase&lt;/code&gt;, and the SQL bulk-ops&lt;br&gt;
layer. Holding all four files in working memory&lt;br&gt;
simultaneously while chasing a deduplication bug in&lt;br&gt;
array hash updates — Copilot did the "where is this&lt;br&gt;
called from, what's the contract" lookup, my brain did&lt;br&gt;
the fix.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Honest fact-checking before publishing.&lt;/strong&gt; I once&lt;br&gt;
asked it to draft a reply claiming "old readers ignore&lt;br&gt;
unknown structures gracefully on multi-version deploys."&lt;br&gt;
It pushed back, we read the actual code together, and&lt;br&gt;
the answer turned out to be "reads are graceful, writes&lt;br&gt;
are destructive, and the production playbook&lt;br&gt;
compensates with runtime drain." That nuance got&lt;br&gt;
published. The first version would have been a small&lt;br&gt;
lie. Same loop surfaced the &lt;code&gt;strictDeleteExtra = true&lt;/code&gt;&lt;br&gt;
default that's been on the fix list ever since.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;RFC-driven implementation (this one mattered a lot&lt;br&gt;
for Identity).&lt;/strong&gt; &lt;code&gt;redb.Identity&lt;/code&gt; is built almost&lt;br&gt;
entirely on top of public RFCs: OAuth 2.1 / 6749,&lt;br&gt;
Token Revocation 7009, Introspection 7662, Dynamic&lt;br&gt;
Client Registration 7591 / 7592, JWK Thumbprint 7638,&lt;br&gt;
SCIM 7644, OAuth for Native Apps 8252, Device&lt;br&gt;
Authorization 8628, Token Exchange 8693, DPoP 9449,&lt;br&gt;
Shared Signals 8417 — and that's not the full list.&lt;br&gt;
For a solo developer, staying strictly compliant with&lt;br&gt;
that many specs is &lt;em&gt;brutal&lt;/em&gt;: every endpoint has&lt;br&gt;
"MUST / SHOULD / MAY" clauses scattered across multiple&lt;br&gt;
§-sections of multiple documents, edge cases like&lt;br&gt;
"what status code on revoke of unknown token" (7009&lt;br&gt;
§2.1: still 200), "must DPoP-Nonce rotate on every&lt;br&gt;
bearing response" (9449 §8: yes), "is &lt;code&gt;dpop_jkt&lt;/code&gt; binding&lt;br&gt;
enforced at the token endpoint" (9449 §10.1: yes) \u2014&lt;br&gt;
miss one and you've shipped a non-compliant auth&lt;br&gt;
server. Copilot was constantly the "wait, RFC 7591 §3.2.1&lt;br&gt;
says the response MUST include a registration access&lt;br&gt;
token, did you cover that?" voice in the room. The&lt;br&gt;
test suite reflects that: 1751+ tests with explicit&lt;br&gt;
&lt;code&gt;"RFC XXXX §Y.Z: ..."&lt;/code&gt; assertion messages, written&lt;br&gt;
in the loop of &lt;em&gt;"I remember the spirit of this clause,&lt;br&gt;
Copilot, pull up the exact wording and let's make a&lt;br&gt;
test out of it."&lt;/em&gt; Without that, achieving real RFC&lt;br&gt;
compliance solo would have been a multi-year sub-project&lt;br&gt;
on its own.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;The pattern that emerged:&lt;/strong&gt; I came in with the design&lt;br&gt;
and most of the code already in place. Copilot saw what&lt;br&gt;
was there, understood the conventions, and helped me&lt;br&gt;
&lt;em&gt;finish&lt;/em&gt;. Polish XML docs. Close out deferred TODOs.&lt;br&gt;
Write the documentation pages. Refactor the inconsistent&lt;br&gt;
bits. Catch the contradictions before they hit a public&lt;br&gt;
comment thread. That's a different relationship than&lt;br&gt;
"AI wrote my project." It's closer to having a very&lt;br&gt;
fast pair-programmer who actually read the codebase&lt;br&gt;
before sitting down.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Honest summary:&lt;/strong&gt; ~385k LOC of infrastructure in three&lt;br&gt;
years of part-time work was the human heroic effort.&lt;br&gt;
&lt;em&gt;Finishing&lt;/em&gt; it — closing out the deferred features,&lt;br&gt;
filling in all the missing comments and documentation,&lt;br&gt;
unifying the naming, writing the READMEs, shipping the&lt;br&gt;
NuGet packages, drafting the dev.to articles, answering&lt;br&gt;
the first hard comment in public — that's where Copilot&lt;br&gt;
collapsed months into weeks. The contest prompt is&lt;br&gt;
"revive and finish a project you started but never&lt;br&gt;
completed." My project wasn't unfinished in design.&lt;br&gt;
It was unfinished in &lt;em&gt;all the small, deferred, boring,&lt;br&gt;
necessary things&lt;/em&gt; that turn a working codebase into a&lt;br&gt;
shippable product. Copilot is what made that finishing&lt;br&gt;
arc actually fit inside one spring.&lt;/p&gt;




&lt;h2&gt;
  
  
  What's next (already in flight, not on a wish-list)
&lt;/h2&gt;

&lt;p&gt;Shipping the four pillars wasn't the finish line — it was&lt;br&gt;
the &lt;em&gt;beginning&lt;/em&gt; of the public phase. Three big bets are&lt;br&gt;
already underway, in source, in this repo:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Free engine is overtaking Pro.&lt;/strong&gt; The&lt;br&gt;
&lt;a href="//../FreePvtQuery/FREE-OVER-PRO.md"&gt;docs/FreePvtQuery&lt;/a&gt;&lt;br&gt;
initiative is rewriting the REDB query path on top of&lt;br&gt;
PostgreSQL PVT functions, and the free tier is already&lt;br&gt;
&lt;em&gt;ahead&lt;/em&gt; of Pro on a long list of features:&lt;br&gt;
&lt;code&gt;$case&lt;/code&gt; / &lt;code&gt;$coalesce&lt;/code&gt; / &lt;code&gt;$cast&lt;/code&gt; / n-ary &lt;code&gt;$concat&lt;/code&gt; in&lt;br&gt;
projections, full regex predicates (&lt;code&gt;$regex&lt;/code&gt;,&lt;br&gt;
&lt;code&gt;$iregex&lt;/code&gt;, &lt;code&gt;$regexReplace&lt;/code&gt;), extended math&lt;br&gt;
(&lt;code&gt;$power&lt;/code&gt;, &lt;code&gt;$sqrt&lt;/code&gt;, &lt;code&gt;$log&lt;/code&gt;, &lt;code&gt;$sin/cos/tan&lt;/code&gt;, ...),&lt;br&gt;
extended string ops (&lt;code&gt;$substring&lt;/code&gt;, &lt;code&gt;$replace&lt;/code&gt;,&lt;br&gt;
&lt;code&gt;$indexOf&lt;/code&gt;, &lt;code&gt;$padLeft/Right&lt;/code&gt;), projection-level&lt;br&gt;
&lt;code&gt;DISTINCT ON&lt;/code&gt;, date-extract in &lt;code&gt;Select&lt;/code&gt;, and as of&lt;br&gt;
2026-05-23 — &lt;code&gt;HAVING&lt;/code&gt; for both regular and array&lt;br&gt;
&lt;code&gt;GroupBy&lt;/code&gt; (33/33 integration tests green across PG&lt;br&gt;
Free + PG Pro + MSSql Pro). Net effect: most of what&lt;br&gt;
used to be Pro-only is becoming free, and the&lt;br&gt;
free engine is gaining capabilities Pro doesn't have&lt;br&gt;
at all.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;REDB outside C#.&lt;/strong&gt; The whole point of building the&lt;br&gt;
query layer as language-neutral JSON AST evaluated by&lt;br&gt;
database-side PVT functions is that &lt;em&gt;the C# LINQ&lt;br&gt;
provider is just one front-end&lt;/em&gt;. The same &lt;code&gt;_objects&lt;/code&gt; +&lt;br&gt;
&lt;code&gt;_values&lt;/code&gt; storage, the same scheme registry, the same&lt;br&gt;
query AST can be driven from Python, from Google Apps&lt;br&gt;
Script, from any language that can speak Postgres or&lt;br&gt;
MSSQL — without porting the engine. That unlocks REDB&lt;br&gt;
as a shared object store across heterogeneous stacks,&lt;br&gt;
not just .NET shops. Architecturally, the work is&lt;br&gt;
already done: every query goes through JSON AST →&lt;br&gt;
PVT functions, no C#-side filter compilation required&lt;br&gt;
for the heavy path.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Route keeps growing.&lt;/strong&gt; New transport connectors are&lt;br&gt;
on the roadmap, deep-dive articles for the Tsak&lt;br&gt;
runtime container and for &lt;code&gt;redb.Identity&lt;/code&gt; are queued&lt;br&gt;
up after the contest deadline, and the EIP catalogue&lt;br&gt;
is being expanded incrementally on top of the&lt;br&gt;
existing 80+ patterns. The fastest-growing transport&lt;br&gt;
(&lt;code&gt;redb.Route.Http&lt;/code&gt;) is also driving a focused docs&lt;br&gt;
pass on the HTTP path specifically.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The honest version: the ecosystem has been moving for&lt;br&gt;
years and barely anyone outside one production deployment&lt;br&gt;
knew. The potential is genuinely large — language-neutral&lt;br&gt;
object storage with a full EIP runtime &lt;em&gt;and&lt;/em&gt; a compliant&lt;br&gt;
identity server on top of the same fabric is not a thing&lt;br&gt;
that currently exists on .NET. That's the bet I'm&lt;br&gt;
finishing.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Team:&lt;/strong&gt; solo submission. All commits, all docs, all&lt;br&gt;
articles by one author with Copilot in the loop.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Live in production:&lt;/strong&gt; 3-node cluster, ~550 internal&lt;br&gt;
users, ~500k objects, ~15M &lt;code&gt;_values&lt;/code&gt; rows, ~2500 hours&lt;br&gt;
uptime, zero data-layer incidents in 3 months.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Stack:&lt;/strong&gt; .NET 8 / 9, PostgreSQL 17, MS SQL 2022,&lt;br&gt;
Blazor Server, Quartz.NET, Npgsql, OpenIddict,&lt;br&gt;
OpenTelemetry.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;License:&lt;/strong&gt; Apache 2.0 across all four pillars.&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>githubchallenge</category>
      <category>dotnet</category>
    </item>
    <item>
      <title>An EF Core alternative for .NET apps with complex object graphs — full LINQ, no migrations, no DbContext</title>
      <dc:creator>rinat kozin</dc:creator>
      <pubDate>Thu, 21 May 2026 17:36:11 +0000</pubDate>
      <link>https://dev.to/rinat_kozin_d0a2ef43e7824/two-tables-zero-migrations-full-linq-a-net-data-engine-thats-been-running-our-production-for-2482</link>
      <guid>https://dev.to/rinat_kozin_d0a2ef43e7824/two-tables-zero-migrations-full-linq-a-net-data-engine-thats-been-running-our-production-for-2482</guid>
      <description>&lt;p&gt;Today I'd like to slow down a bit and talk about &lt;code&gt;redb.Core&lt;/code&gt; — the data engine at the heart of the RedBase ecosystem. The other pieces (&lt;code&gt;redb.Route&lt;/code&gt; for pipelines, &lt;code&gt;redb.Tsak&lt;/code&gt; for cluster runtime) lean on it, but this post is just about the database part.&lt;/p&gt;

&lt;p&gt;I've been working on this project for several years. It started as an attempt to get rid of migrations and turned into what it is now — a typed object store for .NET over PostgreSQL and MSSQL.&lt;/p&gt;

&lt;p&gt;It's not a weekend prototype. The free packages on NuGet are at version 2.0, there are 43 packages across the ecosystem, the architecture went through three rewrites, and as of this week it's been running 3 months on production at a 30-year national food distributor (more on that below).&lt;/p&gt;

&lt;p&gt;This post is a technical walkthrough of &lt;code&gt;redb.Core&lt;/code&gt; — what it is, how it differs from EF Core, what the generated SQL actually looks like, what the production workload looks like, and what's shipping next.&lt;/p&gt;




&lt;h2&gt;
  
  
  What redb.Core actually is
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/redbase-app/redb" rel="noopener noreferrer"&gt;github.com/redbase-app/redb&lt;/a&gt;&lt;/strong&gt; — Apache 2.0&lt;/p&gt;

&lt;p&gt;RedBase stores typed C# objects in &lt;strong&gt;two tables&lt;/strong&gt; (&lt;code&gt;_objects&lt;/code&gt; + &lt;code&gt;_values&lt;/code&gt;) over PostgreSQL or Microsoft SQL Server. Not JSON blobs. Not JSONB. Real typed columns with FK constraints — &lt;code&gt;NUMERIC(38,18)&lt;/code&gt; for money, &lt;code&gt;timestamptz&lt;/code&gt; for dates, &lt;code&gt;uuid&lt;/code&gt; for GUIDs. Real B-tree indexes. ACID transactions.&lt;/p&gt;

&lt;p&gt;The schema is your C# class:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;RedbScheme&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Employee"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;EmployeeProps&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;FirstName&lt;/span&gt;   &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;LastName&lt;/span&gt;    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;    &lt;span class="n"&gt;Age&lt;/span&gt;         &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;decimal&lt;/span&gt; &lt;span class="n"&gt;Salary&lt;/span&gt;     &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;DateTime&lt;/span&gt; &lt;span class="n"&gt;HireDate&lt;/span&gt;  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[]?&lt;/span&gt; &lt;span class="n"&gt;Skills&lt;/span&gt;   &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;Address&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;HomeAddress&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;Dictionary&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;decimal&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;?&lt;/span&gt; &lt;span class="n"&gt;BonusByYear&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That attribute is the entire schema definition. Call &lt;code&gt;SyncSchemeAsync&amp;lt;EmployeeProps&amp;gt;()&lt;/code&gt; once — done. Add a property next sprint — redeploy, call sync again. Old objects still work. No migration files. No DBA ticket. No 2am rollback story.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Save — entire object graph, one call&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;redb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SaveAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;employee&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Load — full graph, arrays, dicts, nested classes — all materialized&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;redb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;LoadAsync&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;EmployeeProps&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Query — real LINQ, real SQL&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;seniors&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;redb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Query&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;EmployeeProps&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Salary&lt;/span&gt; &lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="m"&gt;100_000&lt;/span&gt; &lt;span class="p"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Age&lt;/span&gt; &lt;span class="p"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="m"&gt;35&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;OrderByDescending&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Salary&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Take&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;50&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToListAsync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No &lt;code&gt;DbContext&lt;/code&gt;. No &lt;code&gt;Include&lt;/code&gt; chains. No &lt;code&gt;Add-Migration&lt;/code&gt;. No mapper layer.&lt;/p&gt;




&lt;h2&gt;
  
  
  Object graphs in one call
&lt;/h2&gt;

&lt;p&gt;This is the part that surprises people coming from EF. Props can contain other Props — single references, arrays, dictionaries — and the entire graph saves and loads as one operation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;RedbScheme&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Order"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;OrderProps&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;Customer&lt;/span&gt; &lt;span class="n"&gt;Customer&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;                          &lt;span class="c1"&gt;// nested class&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;Address&lt;/span&gt; &lt;span class="n"&gt;ShippingAddress&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;                    &lt;span class="c1"&gt;// nested class&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;Product&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;Products&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;                         &lt;span class="c1"&gt;// array of classes&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;RedbObject&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;PaymentProps&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;[]&lt;/span&gt; &lt;span class="n"&gt;Payments&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;        &lt;span class="c1"&gt;// array of full objects with own IDs&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;Dictionary&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;RedbObject&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;CouponProps&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Coupons&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;  &lt;span class="c1"&gt;// dict of objects&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;Dictionary&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;Year&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;Quarter&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Reviews&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="c1"&gt;// tuple-key dict&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;redb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SaveAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;   &lt;span class="c1"&gt;// entire graph persisted, FK ordering handled&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;loaded&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;redb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;LoadAsync&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;OrderProps&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// loaded.Props.Customer.Address — ready&lt;/span&gt;
&lt;span class="c1"&gt;// loaded.Props.Payments[0].Props — ready (full RedbObject with own Id, DateCreate, etc.)&lt;/span&gt;
&lt;span class="c1"&gt;// loaded.Props.Coupons["SUMMER20"].Props — ready&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In EF Core this would be 28 tables, ~40 &lt;code&gt;Include&lt;/code&gt;/&lt;code&gt;ThenInclude&lt;/code&gt; calls, manual junction tables for the many-to-many, and INSERT ordering that breaks every time someone adds a non-nullable FK without a default.&lt;/p&gt;

&lt;p&gt;In RedBase: one &lt;code&gt;SaveAsync&lt;/code&gt;, one &lt;code&gt;LoadAsync&lt;/code&gt;. The nested &lt;code&gt;RedbObject&lt;/code&gt; instances are real first-class objects — they have their own IDs, their own timestamps, they can be queried independently, they participate in tree structures. They are not denormalised JSON glued to the parent.&lt;/p&gt;




&lt;h2&gt;
  
  
  What the LINQ actually compiles to
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;Where(e =&amp;gt; e.Salary &amp;gt; 100_000 &amp;amp;&amp;amp; e.Age &amp;gt;= 35)&lt;/code&gt; doesn't get serialized to JSON and re-parsed (that's the Free engine's path — covered later). In Pro, the C# expression tree is walked node-by-node and emitted as parameterized SQL. Roughly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;WITH&lt;/span&gt; &lt;span class="n"&gt;pvt&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id_object&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
         &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;array_agg&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_Numeric&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;FILTER&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id_structure&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;))[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="nv"&gt;"Salary"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
         &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;array_agg&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_Long&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;    &lt;span class="n"&gt;FILTER&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id_structure&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;))[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="nv"&gt;"Age"&lt;/span&gt;
    &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;_values&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;
   &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id_structure&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;ANY&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nb"&gt;bigint&lt;/span&gt;&lt;span class="p"&gt;[])&lt;/span&gt;
     &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id_object&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;_objects&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id_scheme&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
   &lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id_object&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;
  &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;_objects&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;
  &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;pvt&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;pvt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id_object&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id&lt;/span&gt;
 &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;pvt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"Salary"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;
   &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;pvt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"Age"&lt;/span&gt;    &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;
 &lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;pvt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"Salary"&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;
 &lt;span class="k"&gt;LIMIT&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Parameterized. Plan-cached by PostgreSQL. One index scan on &lt;code&gt;(_id_structure, _id_object)&lt;/code&gt;, one aggregation pass, B-tree filter on flat columns. The number of filter fields doesn't change the shape of the query.&lt;/p&gt;

&lt;p&gt;The C# → SQL compiler handles arithmetic (&lt;code&gt;*&lt;/code&gt;, &lt;code&gt;+&lt;/code&gt;, &lt;code&gt;%&lt;/code&gt;), &lt;code&gt;Math.*&lt;/code&gt;, &lt;code&gt;String.Contains/StartsWith/Trim/ToLower&lt;/code&gt;, &lt;code&gt;DateTime.Year/Month/...&lt;/code&gt;, nullable navigation (&lt;code&gt;x.Address?.City&lt;/code&gt;) compiled to &lt;code&gt;IS NOT NULL&lt;/code&gt;, the ternary operator compiled to &lt;code&gt;CASE WHEN&lt;/code&gt;, &lt;code&gt;StringComparison.OrdinalIgnoreCase&lt;/code&gt; compiled to native &lt;code&gt;ILIKE&lt;/code&gt;, dictionary access &lt;code&gt;dict["key"]&lt;/code&gt; compiled to pivot columns, and a few more edge cases that EF Core itself doesn't always handle.&lt;/p&gt;

&lt;p&gt;You can preview the SQL of any query without executing it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;sql&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToSqlStringAsync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Like &lt;code&gt;IQueryable.ToQueryString()&lt;/code&gt; in EF, but works for trees, GroupBy, window functions too.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why bulk save is so fast — two tables, two streams
&lt;/h2&gt;

&lt;p&gt;Something to understand before the change-tracking section: the storage layout is &lt;strong&gt;two tables&lt;/strong&gt;, so &lt;code&gt;SaveAsync&lt;/code&gt; of a batch is &lt;strong&gt;two bulk operations&lt;/strong&gt;, not N round-trips.&lt;/p&gt;

&lt;p&gt;On PostgreSQL the provider uses Npgsql's &lt;code&gt;BeginBinaryImportAsync&lt;/code&gt; — native COPY protocol, binary format:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// from redb.Postgres/Data/NpgsqlBulkOperations.cs&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;var&lt;/span&gt; &lt;span class="n"&gt;writer&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;BeginBinaryImportAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"COPY _objects (_id, _id_parent, _id_scheme, _name, ...) FROM STDIN (FORMAT BINARY)"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;obj&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;objectsList&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;writer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;StartRowAsync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;writer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;        &lt;span class="n"&gt;NpgsqlDbType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Bigint&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;writer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IdScheme&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="n"&gt;NpgsqlDbType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Bigint&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="c1"&gt;// ... typed writes&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;writer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CompleteAsync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For a batch save: one COPY stream writes the &lt;code&gt;_objects&lt;/code&gt; rows, another writes the &lt;code&gt;_values&lt;/code&gt; rows. Two streams, no per-row round-trips, no string-formatted INSERTs. MSSQL uses &lt;code&gt;SqlBulkCopy&lt;/code&gt; for the same role.&lt;/p&gt;

&lt;p&gt;This is why 1000 routes × ~40 fields = ~40,000 value rows save in tens of milliseconds inside the 200–300 ms budget. The bottleneck is the network round-trip and the COPY write, not the ORM machinery — there isn't any ORM machinery in the hot path.&lt;/p&gt;




&lt;h2&gt;
  
  
  Change tracking without DbContext (Pro)
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;DbContext&lt;/code&gt; keeps an in-memory snapshot of every entity you load — that's how it knows what changed. It's also why it isn't thread-safe and why the cache dies with the request.&lt;/p&gt;

&lt;p&gt;RedBase Pro takes a different approach. With &lt;code&gt;PropsSaveStrategy.ChangeTracking&lt;/code&gt; (Pro only; the free tier uses &lt;code&gt;DeleteInsert&lt;/code&gt;), &lt;code&gt;SaveAsync&lt;/code&gt; does this on a batch:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;One bulk SELECT&lt;/strong&gt; of existing values for all object IDs being saved.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Build two &lt;code&gt;ValueTreeNode&lt;/code&gt; trees&lt;/strong&gt; — one from your in-memory objects, one from the DB state.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Structural diff&lt;/strong&gt; — subtrees with matching hashes are skipped entirely (no value comparison, no child traversal). Inserts, updates, and deletes are computed per node.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Three bulk operations&lt;/strong&gt; — &lt;code&gt;BulkInsertValuesAsync&lt;/code&gt;, &lt;code&gt;BulkUpdateValuesAsync&lt;/code&gt;, &lt;code&gt;BulkDeleteValuesAsync&lt;/code&gt; — each a single round-trip. Inserts go through COPY BINARY again.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Net effect: changing one field in a deeply nested object emits one &lt;code&gt;UPDATE&lt;/code&gt;, not a full delete-and-reinsert of the entire props graph. And the comparison happens in C# on the application side — no &lt;code&gt;DbContext&lt;/code&gt; lifetime to worry about, safe to run from a background &lt;code&gt;Channel&lt;/code&gt; consumer or a &lt;code&gt;Parallel.ForEach&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;(In the production code from the previous section, the application also does its &lt;strong&gt;own&lt;/strong&gt; &lt;code&gt;obj.ComputeHash()&lt;/code&gt; check at the route level. That part is optional business-code — you could call &lt;code&gt;SaveAsync&lt;/code&gt; on every route and the Pro tree-diff would still skip unchanged values internally. It's there as a coarse pre-filter so unchanged routes don't even enter the save pipeline at all, saving the diff work too.)&lt;/p&gt;




&lt;h2&gt;
  
  
  Production deployment — the numbers
&lt;/h2&gt;

&lt;p&gt;The biggest deployment right now:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;30-year national HoReCa food distributor&lt;/li&gt;
&lt;li&gt;~150,000 orders/month, ~20,000 B2B customers, 600+ cities&lt;/li&gt;
&lt;li&gt;3-node cluster, 4 cores / 8 GB RAM / 50 GB SSD per node&lt;/li&gt;
&lt;li&gt;~550 daily internal users (operators, drivers, supervisors, dispatch, back-office)&lt;/li&gt;
&lt;li&gt;10–15% CPU under full load&lt;/li&gt;
&lt;li&gt;Integrations: SAP, Kafka, RabbitMQ, GPS feeds, Mercury / EGAIS / government APIs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Three months in production, no data-layer incidents. Two projects in the company use it, the second one came after the first one proved stable.&lt;/p&gt;

&lt;h3&gt;
  
  
  Real workload, real timings
&lt;/h3&gt;

&lt;p&gt;The hottest pipeline is the SAP monitoring sync. Every 60 seconds a SQL polling consumer calls a stored procedure on SAP S/4 (&lt;code&gt;usp_TsUM_MonitoringReport_xml&lt;/code&gt;), gets ~1000 transportation orders back as XML, syncs reference dictionaries (drivers, vehicles, list items), bulk-loads existing routes from RedBase, hash-compares each one, and saves only the changed ones.&lt;/p&gt;

&lt;p&gt;The whole loop fits in ~200–300 ms for ~1000 routes. The actual production code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="nf"&gt;From&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"direct://tsum"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;RouteId&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"tsum-processing"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ProcessWithRedb&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;redb&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;exchange&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;TransportationOrder&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;)&lt;/span&gt;&lt;span class="n"&gt;exchange&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;In&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Body&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;sw&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Stopwatch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;StartNew&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="c1"&gt;// 1. Sync dictionaries (Drivers, Vehicles, ListItems) — only new/changed&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;dicts&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;DictionarySyncService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SyncFromOrdersAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;redb&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dicts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DriversNew&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="n"&gt;dicts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DriversChanged&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="n"&gt;dicts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;VehiclesNew&lt;/span&gt;
            &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="n"&gt;dicts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;VehiclesChanged&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="n"&gt;dicts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ListItemsRelinked&lt;/span&gt; &lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;RefDataCache&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;RefreshAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;redb&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;syncMs&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sw&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ElapsedMilliseconds&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="c1"&gt;// 2. Bulk load existing routes by Code — one query, ~1000 codes&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;codes&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;o&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Code&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;ToList&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;existing&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;redb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Query&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;TransportationRoute&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;()&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WhereRedb&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;o&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;codes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ValueString&lt;/span&gt;&lt;span class="p"&gt;!))&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToListAsync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;existingByCode&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;existing&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToDictionary&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;o&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ValueString&lt;/span&gt;&lt;span class="p"&gt;!,&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="c1"&gt;// 3. Merge: update if hash changed, insert if new, skip if unchanged&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;toSave&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IRedbObject&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;updatedCount&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;skippedCount&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;order&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;routeProps&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;MapOrderToRouteProps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dicts&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;existingByCode&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;TryGetValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Code&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;out&lt;/span&gt; &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;hashBefore&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ComputeHash&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
                &lt;span class="nf"&gt;EnrichRouteFromOrder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Props&lt;/span&gt;&lt;span class="p"&gt;!,&lt;/span&gt; &lt;span class="n"&gt;routeProps&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ComputeHash&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;hashBefore&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;toSave&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="n"&gt;updatedCount&lt;/span&gt;&lt;span class="p"&gt;++;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
                &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="n"&gt;skippedCount&lt;/span&gt;&lt;span class="p"&gt;++;&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="k"&gt;else&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;toSave&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;RedbObject&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;TransportationRoute&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
                &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;$"Route &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Code&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="n"&gt;value_string&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Code&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="n"&gt;Props&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;routeProps&lt;/span&gt;
                &lt;span class="p"&gt;});&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="c1"&gt;// 4. One batched save — mixed inserts + updates&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;toSave&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Count&lt;/span&gt; &lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;redb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SaveAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;toSave&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="n"&gt;Logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;LogInformation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="s"&gt;"[TSUM] orders={Orders} routes(+{Created} ~{Updated} ={Skipped}) "&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt;
            &lt;span class="s"&gt;"drivers(+{DN} ~{DC}) vehicles(+{VN} ~{VC}) "&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt;
            &lt;span class="s"&gt;"sync={Sync} query={Query} save={Save} total={Total}ms"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;orders&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Count&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;toSave&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Count&lt;/span&gt; &lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="n"&gt;updatedCount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;updatedCount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;skippedCount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;dicts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DriversNew&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dicts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DriversChanged&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;dicts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;VehiclesNew&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dicts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;VehiclesChanged&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;syncMs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;queryMs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;saveMs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sw&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ElapsedMilliseconds&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;TransportationRoute&lt;/code&gt; has ~40 fields and 12 &lt;code&gt;RedbListItem&lt;/code&gt; references (Driver, Vehicle, CarMark, ShippingPoint, BusinessType, PlaceTo, PlaceFrom, LoadingZone, TransportStatus, Risk, DeliveryStatus, LoadStatus) plus 2 object references to AD users. Every single one is a foreign key in the database. None of them require a JOIN at query time — the materializer handles it.&lt;/p&gt;

&lt;p&gt;Smaller queries (point-lookup of a single route, dictionary fetch, REST endpoints for the UI) run in &lt;strong&gt;50–100 ms&lt;/strong&gt; including HTTP overhead.&lt;/p&gt;

&lt;h3&gt;
  
  
  What's actually in the database
&lt;/h3&gt;

&lt;p&gt;After running this in production, the storage looks like this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;~1000 transportation routes/day, plus delivery points, transport snapshots, garage states, slice settings, slice snapshots, drivers, vehicles, yard places, AD user refs — about a dozen &lt;code&gt;[RedbScheme]&lt;/code&gt; classes in active use&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;~500,000 objects&lt;/strong&gt; in &lt;code&gt;_objects&lt;/code&gt; (routes, points, snapshots, dictionary items)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;~15,000,000 rows&lt;/strong&gt; in &lt;code&gt;_values&lt;/code&gt; (every typed property of every object)&lt;/li&gt;
&lt;li&gt;All of that lives in &lt;strong&gt;2 tables&lt;/strong&gt; — &lt;code&gt;_objects&lt;/code&gt; and &lt;code&gt;_values&lt;/code&gt; — plus the system tables for schemes, structures, lists, users, permissions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;15 million typed value rows. Two tables. No 30-table schema. No 200 migration files. The query times above are on this dataset.&lt;/p&gt;

&lt;p&gt;If you tried to model the same domain in EF Core flat tables, you'd end up with roughly: &lt;code&gt;Routes&lt;/code&gt;, &lt;code&gt;RoutePoints&lt;/code&gt;, &lt;code&gt;TransportSnapshots&lt;/code&gt;, &lt;code&gt;GarageStates&lt;/code&gt;, &lt;code&gt;SliceSettings&lt;/code&gt;, &lt;code&gt;SliceSnapshots&lt;/code&gt;, &lt;code&gt;Drivers&lt;/code&gt;, &lt;code&gt;Vehicles&lt;/code&gt;, &lt;code&gt;CarMarks&lt;/code&gt;, &lt;code&gt;ShippingPoints&lt;/code&gt;, &lt;code&gt;BusinessTypes&lt;/code&gt;, &lt;code&gt;YardPlaces&lt;/code&gt;, &lt;code&gt;LoadingZones&lt;/code&gt;, &lt;code&gt;TransportStatuses&lt;/code&gt;, &lt;code&gt;DeliveryStatuses&lt;/code&gt;, &lt;code&gt;Risks&lt;/code&gt;, &lt;code&gt;TripRisks&lt;/code&gt;, &lt;code&gt;AdUsers&lt;/code&gt; — plus junction tables and audit tables for each. 30+ tables, dozens of migrations, and every schema change is a deploy event.&lt;/p&gt;

&lt;p&gt;In RedBase the schema change is &lt;code&gt;git push&lt;/code&gt;. The next &lt;code&gt;InitializeAsync()&lt;/code&gt; call adds the new structure rows. Done.&lt;/p&gt;

&lt;h3&gt;
  
  
  What would EF Core look like here?
&lt;/h3&gt;

&lt;p&gt;I asked myself the same question before starting. Let's count what EF would need:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;TransportationRoute&lt;/code&gt; entity → 1 table&lt;/li&gt;
&lt;li&gt;12 &lt;code&gt;ListItem&lt;/code&gt; references → 12 lookup tables + 12 nullable FKs&lt;/li&gt;
&lt;li&gt;2 AD user references → 2 more FKs&lt;/li&gt;
&lt;li&gt;The 1000-route batch update → 1000 entities tracked in &lt;code&gt;DbContext&lt;/code&gt; for change detection&lt;/li&gt;
&lt;li&gt;Hash-based skip of unchanged objects → doesn't exist in EF out of the box; you write it manually&lt;/li&gt;
&lt;li&gt;The same logic across multiple processes/routes → &lt;code&gt;DbContext&lt;/code&gt; is per-request, the change-tracker cache dies at the end of every batch&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The realistic EF flow looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Load existing — needs Include for every lookup or you get N+1&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;existing&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Routes&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Include&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Driver&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Include&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Vehicle&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Include&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CarMark&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Include&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ShippingPoint&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Include&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;BusinessType&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Include&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PlaceTo&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Include&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PlaceFrom&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Include&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;LoadingZone&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Include&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TransportStatus&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Include&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Risk&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Include&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DeliveryStatus&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Include&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;LoadStatus&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Include&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RiskSetBy&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Include&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;KcResponsible&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;codes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Code&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToListAsync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="c1"&gt;// Map, mutate, SaveChanges — 1000 tracked entities, full diff snapshot in RAM&lt;/span&gt;
&lt;span class="c1"&gt;// SaveChanges fires N UPDATE statements (one per row) in a transaction&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SaveChangesAsync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In practice on a workload like this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;code&gt;Include&lt;/code&gt; chain on 14 lookups produces a query that PostgreSQL/MSSQL can't always optimise cleanly (cartesian explosion risk; you split-query and pay multiple round trips).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;SaveChanges&lt;/code&gt; on 1000 modified entities emits 1000 individual UPDATEs unless you reach for &lt;code&gt;EFCore.BulkExtensions&lt;/code&gt; or similar third-party libraries. RedBase ships COPY BINARY for inserts and a batched UPDATE path for updates in the box.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;DbContext&lt;/code&gt; snapshot of 1000 tracked entities, each with 14 included lookups, is real memory pressure. &lt;code&gt;AsNoTracking()&lt;/code&gt; is faster but you lose change detection — you have to re-implement it.&lt;/li&gt;
&lt;li&gt;The 200–300 ms budget on a 4-core container is not realistic in this scenario without significant manual optimisation and probably a separate bulk-update path.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I'm not saying EF can't do it. I'm saying the equivalent EF implementation is more code, more moving parts, and significantly slower without third-party packages. RedBase ships a single &lt;code&gt;SaveAsync(toSave)&lt;/code&gt; and a hash-based skip primitive in the box.&lt;/p&gt;

&lt;p&gt;The hash comparison itself (&lt;code&gt;obj.ComputeHash() != hashBefore&lt;/code&gt;) is application-level business code — it's optional; the Pro tree-diff inside &lt;code&gt;SaveAsync&lt;/code&gt; would skip unchanged values anyway. But used like this it lets the application skip the ~95% of routes that didn't change between SAP polls before they even enter the save pipeline. Combined with COPY BINARY into two tables and the Pro tree-diff for the routes that do change, the whole loop fits the 200–300 ms budget. No equivalent set of built-in primitives exists in EF Core; you either snapshot manually, re-save everything, or pull in third-party bulk packages.&lt;/p&gt;




&lt;h2&gt;
  
  
  What EF Core does with complex objects (vs what RedBase does)
&lt;/h2&gt;

&lt;p&gt;The E000 benchmark uses &lt;code&gt;EmployeeProps&lt;/code&gt; — a realistic model with nested classes, arrays, dictionaries, and &lt;code&gt;RedbObject&lt;/code&gt; references:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;What classic ORM needs&lt;/th&gt;
&lt;th&gt;RedBase&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;~28 tables&lt;/td&gt;
&lt;td&gt;2 tables&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;FK ordering on every INSERT&lt;/td&gt;
&lt;td&gt;Single &lt;code&gt;SaveAsync&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;40+ &lt;code&gt;Include&lt;/code&gt;/&lt;code&gt;ThenInclude&lt;/code&gt; calls&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;LoadAsync&amp;lt;T&amp;gt;(id)&lt;/code&gt; — one line&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Migration file for every new field&lt;/td&gt;
&lt;td&gt;Add property, call &lt;code&gt;SyncSchemeAsync&lt;/code&gt;, done&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;~5,000 INSERTs for 100 employees&lt;/td&gt;
&lt;td&gt;1 &lt;code&gt;BulkInsert&lt;/code&gt; via COPY protocol&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;For 100 complex employees: EF Core ≈ 4,000–6,000 INSERTs across 28 tables in FK order. RedBase: ~3,000 typed value rows, one COPY command.&lt;/p&gt;

&lt;p&gt;And the test results are published — 525 automated tests across all editions and both databases. All green.&lt;/p&gt;




&lt;h2&gt;
  
  
  The query engine: free vs Pro
&lt;/h2&gt;

&lt;p&gt;This is where it gets technically interesting.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Free edition&lt;/strong&gt; compiles your LINQ lambda to a JSON facet format, then calls a plpgsql stored procedure that parses that JSON and generates SQL dynamically. For simple 1–2 field filters, PostgreSQL optimizes correlated EXISTS subqueries well. It works.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pro edition&lt;/strong&gt; walks the C# expression tree directly in C# (&lt;code&gt;ExpressionToSqlCompiler&lt;/code&gt;), emits native parameterized SQL. Same plan every call — PostgreSQL can cache it. 3–10× faster on complex multi-field filters.&lt;/p&gt;

&lt;p&gt;But here's what's happening right now: &lt;strong&gt;I'm porting the Pro PVT CTE query engine into the free tier.&lt;/strong&gt; The plan is &lt;strong&gt;full parity on PVT&lt;/strong&gt; — same CTE shape, same expression coverage, same projection capabilities. Free and Pro will speak the same query dialect.&lt;/p&gt;

&lt;p&gt;The remaining differences between Free and Pro will be elsewhere:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Query plan caching.&lt;/strong&gt; Pro builds parameterized SQL in C# (&lt;code&gt;ExpressionToSqlCompiler&lt;/code&gt;) — same shape every call, PostgreSQL caches the plan once and reuses it. The free tier generates SQL inside plpgsql with literal values inlined (properly escaped to prevent injection), so each call can produce a slightly different plan. Same correctness, different plan-cache behavior.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Materialization.&lt;/strong&gt; Pro has a parallel materializer (multiple values streams hydrated concurrently into the object graph). Free uses a simpler sequential path.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Save strategy.&lt;/strong&gt; &lt;code&gt;PropsSaveStrategy.ChangeTracking&lt;/code&gt; (tree-diff with hash-based subtree skip) is Pro-only. Free uses &lt;code&gt;DeleteInsert&lt;/code&gt; — still bulk via COPY, but no per-field diff.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So Pro stays faster on hot paths and large object graphs, but the query language itself — what you can write in &lt;code&gt;.Where&lt;/code&gt;, &lt;code&gt;.Select&lt;/code&gt;, projections, grouping, having, FTS, regex — will be the same on both sides.&lt;/p&gt;




&lt;h2&gt;
  
  
  Show me the SQL it generates
&lt;/h2&gt;

&lt;p&gt;Here's a real example — a projection query on &lt;code&gt;EmployeeProps&lt;/code&gt; with 16 computed columns:&lt;br&gt;
full names, salary calculations, date extractions, seniority classification via CASE, coalesce for nullable fields, explicit type cast. This is the &lt;strong&gt;free tier&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;WITH&lt;/span&gt; &lt;span class="n"&gt;_pvt_cte&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;SELECT&lt;/span&gt;
        &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id_object&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;array_agg&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_Long&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;    &lt;span class="n"&gt;FILTER&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id_structure&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1000017&lt;/span&gt;&lt;span class="p"&gt;))[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="nv"&gt;"Age"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;array_agg&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_Numeric&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;FILTER&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id_structure&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1000020&lt;/span&gt;&lt;span class="p"&gt;))[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="nv"&gt;"Salary"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;array_agg&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_DateTimeOffset&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;FILTER&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id_structure&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1000018&lt;/span&gt;&lt;span class="p"&gt;))[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="nv"&gt;"HireDate"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;array_agg&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_String&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="n"&gt;FILTER&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id_structure&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1000016&lt;/span&gt;&lt;span class="p"&gt;))[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="nv"&gt;"LastName"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;array_agg&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_String&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="n"&gt;FILTER&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id_structure&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1000015&lt;/span&gt;&lt;span class="p"&gt;))[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="nv"&gt;"FirstName"&lt;/span&gt;
    &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;_values&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;
    &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id_structure&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;ANY&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ARRAY&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1000015&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;1000016&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;1000017&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;1000018&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;1000020&lt;/span&gt;&lt;span class="p"&gt;]::&lt;/span&gt;&lt;span class="nb"&gt;bigint&lt;/span&gt;&lt;span class="p"&gt;[])&lt;/span&gt;
      &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id_object&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;_objects&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id_scheme&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1000014&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id_object&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt;
    &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id&lt;/span&gt;                                                          &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;"FirstName"&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="s1"&gt;' '&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nv"&gt;"LastName"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;                            &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;full_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;UPPER&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;"FirstName"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;                                             &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;upper_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;LENGTH&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;"FirstName"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;                                            &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;name_len&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;"Salary"&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;                                                &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;yearly_x12&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nv"&gt;"Salary"&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;"Salary"&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;013&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;                      &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;bonus_after_tax&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;"Salary"&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;"Age"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;                                       &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;avg_per_year&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;"Age"&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;                                                    &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;age_mod10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;ABS&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nv"&gt;"Age"&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;35&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;                                              &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;abs_diff&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;FLOOR&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nv"&gt;"Salary"&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;                                          &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;floor_half_salary&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;EXTRACT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;YEAR&lt;/span&gt;  &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="nv"&gt;"HireDate"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;                                 &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;hire_year&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;EXTRACT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;MONTH&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="nv"&gt;"HireDate"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;                                 &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;hire_month&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;EXTRACT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;YEAR&lt;/span&gt;  &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;AGE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;"HireDate"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'2026-05-20'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;              &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;tenure_years&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;CASE&lt;/span&gt;
        &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;"Age"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;THEN&lt;/span&gt; &lt;span class="s1"&gt;'senior'&lt;/span&gt;
        &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;"Age"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;35&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;THEN&lt;/span&gt; &lt;span class="s1"&gt;'mid'&lt;/span&gt;
        &lt;span class="k"&gt;ELSE&lt;/span&gt; &lt;span class="s1"&gt;'junior'&lt;/span&gt;
    &lt;span class="k"&gt;END&lt;/span&gt;                                                            &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;seniority&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;COALESCE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;"FirstName"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'&amp;lt;unknown&amp;gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;                             &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;display_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;"Age"&lt;/span&gt;&lt;span class="p"&gt;)::&lt;/span&gt;&lt;span class="nb"&gt;text&lt;/span&gt;                                                  &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;age_as_text&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;_pvt_cte&lt;/span&gt;
&lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;_objects&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_pvt_cte&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_id_object&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="nv"&gt;"Salary"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'0'&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nb"&gt;numeric&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="nv"&gt;"Salary"&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;
&lt;span class="k"&gt;LIMIT&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One &lt;code&gt;_values&lt;/code&gt; scan. One &lt;code&gt;GROUP BY&lt;/code&gt;. Sixteen computed output columns. Standard SQL — no magic, fully readable in &lt;code&gt;pgAdmin&lt;/code&gt;, fully &lt;code&gt;EXPLAIN ANALYZE&lt;/code&gt;-able.&lt;/p&gt;

&lt;p&gt;This query was generated by &lt;code&gt;pvt_build_projection_sql()&lt;/code&gt; — a plpgsql function that is shipping in the next free release.&lt;/p&gt;




&lt;h2&gt;
  
  
  When this fits and when it doesn't
&lt;/h2&gt;

&lt;p&gt;Honest take.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fits well:&lt;/strong&gt; business apps with collections, nested structures, hierarchies, dictionaries, or schemas that change often. Apps where a business analyst can ask "add a field" and you want to ship the same day. Anything where you'd otherwise end up with 28 tables for one logical entity.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Doesn't fit:&lt;/strong&gt; flat reporting databases with two or three fixed tables. Use Dapper there — it's honest and fast.&lt;/p&gt;

&lt;p&gt;The common pushback is: &lt;em&gt;"EF Core is good enough for 80% of projects."&lt;/em&gt; We'd flip that around: RedBase fits &lt;strong&gt;80% of typical business projects&lt;/strong&gt; — anything with collections, nested structures, lookup tables, hierarchies, dictionaries, or a schema that evolves alongside the product. The 20% where it's overkill is the genuinely flat case: two or three fixed tables, pure reporting, no evolving model. Use Dapper there; it's honest and fast. For everything else — which is most business software — the EF migration tax is real and it compounds with every sprint.&lt;/p&gt;

&lt;p&gt;Three more axes that usually get left out of that comparison:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Extensibility.&lt;/strong&gt; Adding a field is a property in C# and one &lt;code&gt;InitializeAsync()&lt;/code&gt; call — no migration script, no review, no deploy window. The same change in an EF stack is at minimum: model edit + &lt;code&gt;Add-Migration&lt;/code&gt; + reviewed SQL + coordinated deploy. Multiply by every iteration in a year.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Floor skill level.&lt;/strong&gt; With RedBase, a junior developer who knows C# can add entities, fields, queries, and relationships on day one — the schema is just a class, the query is just LINQ. With EF Core on a complex model, you need to understand N+1, cartesian explosion from &lt;code&gt;Include&lt;/code&gt; chains, &lt;code&gt;DbContext&lt;/code&gt; lifetime, migration conflicts on shared branches, and when to reach for &lt;code&gt;AsNoTracking()&lt;/code&gt;. That knowledge takes months to build and years to apply consistently. RedBase moves that complexity into the framework and out of the application code.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cost of development.&lt;/strong&gt; On the TsUM project the data-layer work — schemas, storage, queries, sync pipelines, change-tracking, bulk save — came in at roughly &lt;strong&gt;128 person-hours&lt;/strong&gt;. The estimate for the same scope on a classic EF + handwritten bulk-update + ASP.NET Identity + 30-table migration stack was on the order of &lt;strong&gt;3000 person-hours&lt;/strong&gt;. That's not a typo and it's not marketing — it's the gap between "add a property, redeploy" and "add a column, write migration, update DTOs, update mapper, update repository, write tests for all of it". The 80%/20% framing only holds if you count lines of code; once you count engineer-hours over a project lifetime it inverts.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  What else is in the box
&lt;/h2&gt;

&lt;p&gt;195+ working examples in the repo. The shortlist of things people usually don't expect to be built-in:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Tree queries — recursive CTE underneath, no CTE to write yourself&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;products&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;redb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TreeQuery&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Product&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="n"&gt;londonHQ&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;maxDepth&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;InStock&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToListAsync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="c1"&gt;// Soft delete with atomic mark + background purge + progress in DB&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;redb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SoftDeleteAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ids&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Also shipping in the core (this is the short list — the real list is much longer):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;GroupBy + window functions in one query&lt;/li&gt;
&lt;li&gt;Built-in users / roles / permissions (no ASP.NET Identity dependency, no separate auth schema)&lt;/li&gt;
&lt;li&gt;Export / import via &lt;code&gt;.redb&lt;/code&gt; files (PostgreSQL ↔ MSSQL portability)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Multi-database in one process&lt;/strong&gt; with full domain isolation — separate connections, separate scheme caches, separate object caches per domain; one app can talk to several independent RedBase databases at the same time without their metadata leaking into each other&lt;/li&gt;
&lt;li&gt;Scheme cache and object cache (both per-domain), invalidated on &lt;code&gt;SyncSchemeAsync&lt;/code&gt; / &lt;code&gt;SaveAsync&lt;/code&gt;, used by the materializer to skip repeated metadata lookups&lt;/li&gt;
&lt;li&gt;Atomic soft-delete with background purge and progress visible in the DB&lt;/li&gt;
&lt;li&gt;Tree queries with recursive CTE&lt;/li&gt;
&lt;li&gt;Polymorphic trees — different C# types at each level of the same tree&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There's a lot more that doesn't fit in one section. The architecture page has the full map.&lt;/p&gt;




&lt;h2&gt;
  
  
  What's coming next
&lt;/h2&gt;

&lt;p&gt;The &lt;strong&gt;FreePvtQuery&lt;/strong&gt; release is the nearest milestone. This is a complete rewrite of the free tier's query engine based on the PVT CTE architecture — bringing it to full parity with Pro on the query side:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Single-pass &lt;code&gt;_values&lt;/code&gt; scan (vs correlated EXISTS per field)&lt;/li&gt;
&lt;li&gt;Full expression system: arithmetic, string, date, math, regex, FTS, CASE, coalesce, cast&lt;/li&gt;
&lt;li&gt;Projections with arbitrary computed columns (like the SQL above)&lt;/li&gt;
&lt;li&gt;HAVING, DISTINCT ON, GROUP BY extensions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;After that: MSSQL support for the new engine, C# LINQ-to-JSON bridge so you keep writing &lt;code&gt;.Where(e =&amp;gt; e.Salary &amp;gt; 100_000)&lt;/code&gt; without touching JSON. Pro will keep its edge on plan caching (parameterized SQL), parallel materialization, and tree-diff change tracking.&lt;/p&gt;




&lt;h2&gt;
  
  
  This is maybe 10% of what's in there
&lt;/h2&gt;

&lt;p&gt;This post covered the basics: storage model, query engine, free PVT demo, production deployment. Topics I didn't touch:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Polymorphic tree hierarchies (different C# types at every level, same tree API)&lt;/li&gt;
&lt;li&gt;Object graphs with &lt;code&gt;RedbObject&amp;lt;T&amp;gt;&lt;/code&gt; references inside Props&lt;/li&gt;
&lt;li&gt;Data migrations (Pro): fluent API, computed columns, in-place type changes&lt;/li&gt;
&lt;li&gt;Export/import: &lt;code&gt;.redb&lt;/code&gt; files (JSONL/ZIP), PostgreSQL ↔ MSSQL portability&lt;/li&gt;
&lt;li&gt;Multi-database in one process (domain-isolated caches)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;redb.CLI&lt;/code&gt; — schema management from command line&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;redb.PropsEditor&lt;/code&gt; — runtime props editing UI&lt;/li&gt;
&lt;li&gt;Integration with &lt;code&gt;redb.Route&lt;/code&gt; (22-transport pipeline engine) — separate article&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If there's a direction you want covered next — migrations, polymorphic trees, benchmarks, internals of the diff-tree change tracking — drop it in the comments and that's what the next post will be.&lt;/p&gt;




&lt;h2&gt;
  
  
  Links
&lt;/h2&gt;

&lt;p&gt;There's a lot of material. If you want to go deep without reading three articles, paste these into your AI of choice and ask it to analyse the architecture:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://redbase.app/architecture" rel="noopener noreferrer"&gt;redbase.app/architecture&lt;/a&gt; — expression compiler, PVT CTE, parallel materialization, diff tree save, caching (with actual class names)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://redbase.app/why-redbase" rel="noopener noreferrer"&gt;redbase.app/why-redbase&lt;/a&gt; — 14 pain points, side-by-side SQL&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://redbase.app/examples" rel="noopener noreferrer"&gt;redbase.app/examples&lt;/a&gt; — 195+ examples, every one runs against a real DB&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://redbase.app/results" rel="noopener noreferrer"&gt;redbase.app/results&lt;/a&gt; — 525 tests, MS/PG × Free/Pro, with timings&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/redbase-app/redb" rel="noopener noreferrer"&gt;github.com/redbase-app/redb&lt;/a&gt; — source, Apache 2.0&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Questions in the comments — I'll answer them directly.&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>csharp</category>
      <category>database</category>
      <category>opensource</category>
    </item>
    <item>
      <title>redb.Route — Apache Camel for .NET: 22 transports, 30+ EIP patterns, compiled DSL</title>
      <dc:creator>rinat kozin</dc:creator>
      <pubDate>Sun, 17 May 2026 10:26:13 +0000</pubDate>
      <link>https://dev.to/rinat_kozin_d0a2ef43e7824/redbroute-apache-camel-for-net-22-transports-30-eip-patterns-compiled-dsl-11m0</link>
      <guid>https://dev.to/rinat_kozin_d0a2ef43e7824/redbroute-apache-camel-for-net-22-transports-30-eip-patterns-compiled-dsl-11m0</guid>
      <description>&lt;p&gt;Apache Camel has been solving enterprise integration on the JVM since 2007 — 22k stars, 300+ transports, hundreds of production deployments at banks, telcos, governments. The .NET ecosystem never got a real equivalent. MassTransit and Wolverine cover message-bus and saga scenarios well, but they aren't pipeline engines and they don't pretend to be.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/redbase-app/redb-route" rel="noopener noreferrer"&gt;redb.Route&lt;/a&gt; is the missing piece: a fluent C# DSL that wires Kafka, RabbitMQ, Redis, SQL, HTTP, gRPC, SFTP, MQTT, S3 and 14 more transports through &lt;code&gt;From → Process → To&lt;/code&gt; pipelines, with 30+ Enterprise Integration Patterns and a compiled expression engine. Apache 2.0, .NET 8 / 9 / 10. This post is a technical walkthrough.&lt;/p&gt;




&lt;h2&gt;
  
  
  The shape of every pipeline
&lt;/h2&gt;

&lt;p&gt;Every redb.Route pipeline is &lt;code&gt;From → [processors] → To&lt;/code&gt;. Messages flow as &lt;code&gt;IExchange&lt;/code&gt; instances carrying a body, headers, and properties.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="nf"&gt;From&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"kafka://orders?groupId=svc&amp;amp;brokers=localhost:9092"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;Header&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;isEqualTo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"new"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;To&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"rabbitmq://events?host=localhost"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For complex routing, group routes into a &lt;code&gt;RouteBuilder&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;OrderRoutes&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;RouteBuilder&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Configure&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;From&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"kafka://orders?groupId=svc&amp;amp;brokers=localhost:9092"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;RouteId&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"order-pipeline"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Choice&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;When&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;Header&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"priority"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;isEqualTo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"high"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
                    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"High priority order"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;To&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"direct://fast-lane"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;When&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;Header&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"priority"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;isEqualTo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"low"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
                    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;To&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"seda://batch-queue"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Otherwise&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;To&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"direct://standard"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;EndChoice&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="nf"&gt;From&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"direct://fast-lane"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Retry&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SetHeader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"processed-at"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;DateTimeOffset&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UtcNow&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;exchange&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;exchange&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;In&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Body&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                &lt;span class="n"&gt;exchange&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;In&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Body&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;$"PROCESSED: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="p"&gt;})&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;To&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"rabbitmq://processed?host=localhost"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Registration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddRedbRoute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;route&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;route&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddRouteBuilder&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;OrderRoutes&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;());&lt;/span&gt;
&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddRedbRouteKafka&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddRedbRouteRabbitMQ&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  22 transports as first-class URI schemes
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;kafka://          rabbitmq://       redis://          sql://
http://           grpc://           sftp://           ftp://
mqtt://           s3://             ibmmq://          amqp://
azuresb://        elasticsearch://  firebase://       ldap://
mail://           tcp://            websocket://      signalr://
cron://           file://

+ built-in: direct://   seda://   timer://   log://   mock://
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every transport uses the same &lt;code&gt;IExchange&lt;/code&gt; contract. Swapping Kafka for RabbitMQ means changing the &lt;code&gt;From&lt;/code&gt; URI — the pipeline logic is unchanged.&lt;/p&gt;

&lt;p&gt;Type-safe fluent builders are available as an alternative to URI strings:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;source&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Kafka&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Topic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"orders"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Brokers&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"broker1:9092,broker2:9092"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GroupId&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"order-svc"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Acks&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"All"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nf"&gt;From&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;source&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;To&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"seda://internal"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  EIP patterns as DSL steps
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Content-Based Router&lt;/strong&gt; — route by message content:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="nf"&gt;From&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"kafka://events"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Choice&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;When&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;Header&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;isEqualTo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"order"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;   &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;To&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"direct://orders"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;When&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;Header&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;isEqualTo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"invoice"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;To&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"direct://invoices"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Otherwise&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;                                &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;To&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"seda://unclassified"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;EndChoice&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Splitter + Aggregator&lt;/strong&gt; — split a batch, process each item, re-aggregate by correlation key. Sequential by default; one method turns the split into a bounded-parallel pipeline:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="nf"&gt;From&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"seda://batch"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;Body&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;                                     &lt;span class="c1"&gt;// Body() helper — split the IEnumerable body&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ParallelProcessing&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;                          &lt;span class="c1"&gt;// process items concurrently&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;MaxDegreeOfParallelism&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;8&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;                     &lt;span class="c1"&gt;// bounded fan-out&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;EnrichOrder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;To&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"direct://enriched"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;EndSplit&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="nf"&gt;From&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"direct://enriched"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Aggregate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;Header&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"batch-id"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;ListAggregationStrategy&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;To&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"rabbitmq://processed-batches"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For true fan-out to multiple endpoints in parallel use &lt;strong&gt;Multicast&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="nf"&gt;From&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"kafka://orders"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Multicast&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"direct://pricing"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"direct://inventory"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"direct://fraud-check"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;WireTap&lt;/strong&gt; — copy every message to an audit sink without interrupting the main flow:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="nf"&gt;From&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"kafka://transactions"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WireTap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"direct://audit-log"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;ProcessTransaction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;To&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"rabbitmq://completed"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Idempotent Consumer&lt;/strong&gt; — deduplicate across a cluster. Plug in the repository (in-memory, SQL, or redb.Core EAV) and the same message ID is rejected on every node:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="nf"&gt;From&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"kafka://payments"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;IdempotentConsumer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;e&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;In&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetHeader&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="s"&gt;"payment-id"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;RedbIdempotentRepository&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;redbService&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;   &lt;span class="c1"&gt;// cluster-wide, two-phase commit&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;ProcessPayment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Saga&lt;/strong&gt; — pipeline-level choreography with compensating steps. If &lt;code&gt;ChargePayment&lt;/code&gt; throws, &lt;code&gt;ReleaseInventory&lt;/code&gt; runs automatically. No external state-machine framework required:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="nf"&gt;From&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"direct://checkout"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Saga&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Step&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;     &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;inventory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Reserve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="n"&gt;compensate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;inventory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Release&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Step&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;     &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;payments&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Charge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="n"&gt;compensate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;payments&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Refund&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Step&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;     &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;shipments&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="n"&gt;compensate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;shipments&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Cancel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;OnCompletion&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;PublishOrderCompleted&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;To&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"rabbitmq://order-confirmed"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Full catalogue: Filter, Choice, Splitter, Aggregator, Multicast, WireTap, Recipient List, Dynamic Router, Resequencer, Scatter-Gather, Claim Check, Idempotent Consumer, Saga, Circuit Breaker, Throttle, Retry, Dead Letter, Loop, Delay, Debounce, Enrich, Timeout, TryCatch, Transacted, Process, Validate. &lt;strong&gt;30+ patterns, all first-class DSL.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Error handling — four composable layers
&lt;/h2&gt;

&lt;p&gt;Most .NET libraries give you one mechanism for failures: a retry policy on the consumer. redb.Route exposes four, designed to compose: per-step &lt;code&gt;Retry&lt;/code&gt;, scoped &lt;code&gt;DoTry/DoCatch&lt;/code&gt;, route-local &lt;code&gt;OnException&lt;/code&gt;, and global &lt;code&gt;OnException&lt;/code&gt; declared at the &lt;code&gt;RouteBuilder&lt;/code&gt; level. Plus &lt;code&gt;DeadLetterChannel&lt;/code&gt; for messages that exhaust retries.&lt;/p&gt;

&lt;h3&gt;
  
  
  Dead Letter Channel + a DLQ sub-route that knows why it failed
&lt;/h3&gt;

&lt;p&gt;The failing exception travels with the exchange. The DLQ sub-route can read it, branch on the type, log structured info, archive, retry later:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="nf"&gt;From&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"kafka://orders"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;DeadLetterChannel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"seda://orders-dlq"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Retry&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;ProcessOrder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;To&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"rabbitmq://processed"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// The DLQ is a real route — inspect, branch, react&lt;/span&gt;
&lt;span class="nf"&gt;From&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"seda://orders-dlq"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"DLQ: ${header.correlationId} — ${exception.message}"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Choice&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;When&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetException&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="n"&gt;TimeoutException&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Delay&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TimeSpan&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;FromMinutes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;5&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;To&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"seda://retry-later"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;When&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetException&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="n"&gt;HttpRequestException&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;To&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"sftp://archive/http-failures/"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Otherwise&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;To&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"sftp://archive/poison/"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;EndChoice&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  OnException — per-exception redelivery with exponential backoff
&lt;/h3&gt;

&lt;p&gt;Declared globally at &lt;code&gt;RouteBuilder&lt;/code&gt; level (applies to every route in the builder) or scoped to a single route. Each block configures attempts, delay, backoff, and what to do when the handler succeeds:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;OrderRoutes&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;RouteBuilder&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Configure&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Global — applies to every From(...) below&lt;/span&gt;
        &lt;span class="n"&gt;OnException&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;HttpRequestException&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;()&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;MaximumRedeliveries&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;RedeliveryDelay&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TimeSpan&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;FromSeconds&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;UseExponentialBackOff&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;BackOffMultiplier&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;2.0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Handled&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;                          &lt;span class="c1"&gt;// mark as handled — exchange continues normally&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;To&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"seda://http-failures"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;EndOnException&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="n"&gt;OnException&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;DbException&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;()&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;MaximumRedeliveries&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;UseOriginalMessage&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;               &lt;span class="c1"&gt;// restore original body before sending to handler&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;OnWhen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;!((&lt;/span&gt;&lt;span class="n"&gt;DbException&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetException&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="n"&gt;Message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"deadlock"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;To&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"seda://db-failures"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;EndOnException&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="c1"&gt;// Multiple exception types in one block&lt;/span&gt;
        &lt;span class="nf"&gt;OnException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TimeoutException&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="k"&gt;typeof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;SocketException&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;MaximumRedeliveries&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;RedeliveryDelay&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TimeSpan&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;FromSeconds&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;To&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"seda://network-failures"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;EndOnException&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="nf"&gt;From&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"kafka://orders"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;To&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"http://payments-svc/charge"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="nf"&gt;From&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"kafka://shipments"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;To&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"http://logistics-svc/dispatch"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;Handled()&lt;/code&gt;, &lt;code&gt;Continued()&lt;/code&gt;, &lt;code&gt;OnWhen(predicate)&lt;/code&gt;, &lt;code&gt;RetryWhile(predicate)&lt;/code&gt;, &lt;code&gt;UseOriginalMessage()&lt;/code&gt; — all standard Camel error-handling primitives, and none of the .NET alternatives ship them as DSL.&lt;/p&gt;

&lt;h3&gt;
  
  
  TryCatch — scoped try/catch/finally inside a pipeline
&lt;/h3&gt;

&lt;p&gt;When only one section of a route needs special handling:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;DoTry&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;To&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"http://external-api/submit"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;PostProcess&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DoCatch&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;HttpRequestException&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"HTTP failure: ${exception.message}"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;To&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"seda://retry-queue"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DoCatch&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;TimeoutException&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;To&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"sftp://archive/timeouts/"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;DoFinally&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Attempt complete"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;End&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Compiled expression engine
&lt;/h2&gt;

&lt;p&gt;This is the one feature that distinguishes redb.Route from both Apache Camel and every .NET alternative. Inline expressions — string templates, arithmetic, comparisons, JSONPath, XPath — are translated to real &lt;code&gt;Func&amp;lt;IExchange, T&amp;gt;&lt;/code&gt; delegates via &lt;code&gt;System.Linq.Expressions&lt;/code&gt; at route-build time. No interpreter, no per-message parsing.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// String templates&lt;/span&gt;
&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SetBody&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;Expr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"${header.orderId}-${body}"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SetHeader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"trace"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;Expr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"${header.source}-${header.correlationId}"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="c1"&gt;// Pre/post-increment&lt;/span&gt;
&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SetHeader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"attempt"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;Expr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"${header.attempt++}"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;   &lt;span class="c1"&gt;// returns old value&lt;/span&gt;
&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SetHeader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"attempt"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;Expr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"${++header.attempt}"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;   &lt;span class="c1"&gt;// returns new value&lt;/span&gt;

&lt;span class="c1"&gt;// Arithmetic&lt;/span&gt;
&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SetHeader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"total"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;Expr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"${header.qty * header.price}"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SetHeader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"net"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;   &lt;span class="nf"&gt;Expr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"${header.gross - header.tax}"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="c1"&gt;// Predicates — fluent on top of compiled expressions&lt;/span&gt;
&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;Expr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"header.amount"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;isGreaterThan&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;When&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;Header&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;isEqualTo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"active"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Apache Camel's Simple Language is interpreted at every message dispatch. MassTransit, Wolverine and NServiceBus have &lt;strong&gt;no expression engine at all&lt;/strong&gt; — every conditional is a hand-written C# lambda. With redb.Route you get both: terse string DSL for configuration-driven rules &lt;strong&gt;and&lt;/strong&gt; strongly typed lambdas where you want them, with the same zero-overhead delegate at the bottom.&lt;/p&gt;

&lt;p&gt;The engine supports 9 value types and 17 predicates, and the result of every &lt;code&gt;Expr(...)&lt;/code&gt; is cached per route. You pay for parsing once at startup.&lt;/p&gt;




&lt;h2&gt;
  
  
  Transactional pipelines
&lt;/h2&gt;

&lt;p&gt;A pipeline can wrap several steps in a single transaction. &lt;code&gt;.Transacted()&lt;/code&gt; opens a &lt;code&gt;TransactionScope&lt;/code&gt;; transports that implement &lt;code&gt;ITransactedAction&lt;/code&gt; enlist into it. That means the Kafka commit, the RabbitMQ publisher confirm, and the SQL &lt;code&gt;UPDATE&lt;/code&gt; all succeed or fail together — no half-processed messages, no manual two-phase coordination:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="nf"&gt;From&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Kafka&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Topic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"orders"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Brokers&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"broker:9092"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GroupId&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"order-svc"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;IsolationLevel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"ReadCommitted"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;EnableAutoCommit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;                      &lt;span class="c1"&gt;// commit driven by .Transacted()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Transacted&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;Validate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;To&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Sql&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"INSERT INTO orders (...) VALUES (...)"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
           &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;DataSource&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"main"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
           &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Transacted&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;                              &lt;span class="c1"&gt;// enlists into the same scope&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;To&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Rabbit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Queue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"order-events"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
              &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Confirms&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;                        &lt;span class="c1"&gt;// publisher confirm before commit&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;MassTransit, NServiceBus and Wolverine all solve this for their own bus, but only for the &lt;em&gt;bus&lt;/em&gt;. redb.Route makes it work across &lt;strong&gt;any combination of transports&lt;/strong&gt; that implement &lt;code&gt;ITransactedAction&lt;/code&gt; — Kafka EOS, RabbitMQ tx channels, IBM MQ, AMQP 1.0, SQL.&lt;/p&gt;




&lt;h2&gt;
  
  
  Outbox without an outbox framework
&lt;/h2&gt;

&lt;p&gt;The transactional outbox pattern is usually presented as a feature you opt into via a framework (MassTransit, NServiceBus, Wolverine all bundle one). In redb.Route it's just four lines composed from existing primitives — SQL polling, transactional sink, idempotent consumer:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="nf"&gt;From&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Sql&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Poll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"SELECT id, payload FROM outbox WHERE processed = 0 LIMIT 100"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;DataSource&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"main"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;OnSuccess&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"UPDATE outbox SET processed = 1 WHERE id = ANY(@ids)"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Transacted&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;                                  &lt;span class="c1"&gt;// atomic claim + publish&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;Body&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;IdempotentConsumer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;e&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;In&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetHeader&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="s"&gt;"eventId"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;RedbIdempotentRepository&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;redbService&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;      &lt;span class="c1"&gt;// dedup on republish&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;To&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Kafka&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Topic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"events"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
             &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;EnableTransactionalProducer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
             &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Acks&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"All"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;                             &lt;span class="c1"&gt;// exactly-once on the broker&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No magic table conventions, no separate &lt;code&gt;IOutbox&lt;/code&gt; interface to register, no required ORM. It's pipelines all the way down.&lt;/p&gt;




&lt;h2&gt;
  
  
  Request-Response — HTTP and gRPC as first-class endpoints
&lt;/h2&gt;

&lt;p&gt;The same DSL that handles fire-and-forget Kafka also handles synchronous RPC. Mark the listener &lt;code&gt;InOut()&lt;/code&gt;, set &lt;code&gt;In.Body&lt;/code&gt; to the response anywhere in the pipeline — the HTTP transport sends it back to the caller:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;OrderApi&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;RouteBuilder&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Configure&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;From&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Listen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/api/orders"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;Port&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;8080&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;InOut&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Unmarshal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;JsonMessageSerializer&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="k"&gt;typeof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CreateOrderRequest&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Validate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;In&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Body&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;CreateOrderRequest&lt;/span&gt;&lt;span class="p"&gt;)?.&lt;/span&gt;&lt;span class="n"&gt;Amount&lt;/span&gt; &lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                      &lt;span class="s"&gt;"Amount must be positive"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;req&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CreateOrderRequest&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;In&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Body&lt;/span&gt;&lt;span class="p"&gt;!;&lt;/span&gt;
                &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;orderService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CreateAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;In&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Body&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;                      &lt;span class="c1"&gt;// HTTP transport returns In.Body to the caller&lt;/span&gt;
            &lt;span class="p"&gt;})&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WireTap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"kafka://order-created"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;          &lt;span class="c1"&gt;// audit — fire-and-forget, non-blocking&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Marshal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;JsonMessageSerializer&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Replace &lt;code&gt;Http.Listen&lt;/code&gt; with &lt;code&gt;Grpc.Listen&lt;/code&gt; or &lt;code&gt;Ws.Listen&lt;/code&gt; — same pipeline. RPC, validation, business logic, audit, and serialization in one declarative route. No separate controller layer, no separate consumer layer.&lt;/p&gt;




&lt;h2&gt;
  
  
  Testing without a broker — &lt;code&gt;mock://&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;mock:&lt;/code&gt; transport records every message it receives so unit tests can assert against an in-memory endpoint. No Kafka container, no RabbitMQ container, no Testcontainers — a plain &lt;code&gt;Host&lt;/code&gt; and a few lines of xUnit:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Fact&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt; &lt;span class="nf"&gt;Filter_only_forwards_new_orders&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;host&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Host&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CreateDefaultBuilder&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ConfigureServices&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddRedbRoute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;route&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;route&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddRoutes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;From&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"direct://input"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;Header&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;isEqualTo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"new"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
                &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;To&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"mock://received"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;})))&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Build&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;StartAsync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;producer&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetRequiredService&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IRouteProducer&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;mock&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetRequiredService&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;MockComponent&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;().&lt;/span&gt;&lt;span class="nf"&gt;GetEndpoint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"received"&lt;/span&gt;&lt;span class="p"&gt;)!;&lt;/span&gt;

    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;producer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SendAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"direct://input"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"payload-1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;Dictionary&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;object&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"new"&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;producer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SendAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"direct://input"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"payload-2"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;Dictionary&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;object&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"old"&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="n"&gt;Assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;mock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ReceivedCount&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;Assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"payload-1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;mock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ReceivedExchanges&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;In&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Body&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For async routes use &lt;code&gt;MockDsl.Endpoint("name").ExpectedMessageCount(n)&lt;/code&gt; — it awaits the expected count with a timeout. This is the Camel testing idiom and one of the reasons unit tests on Camel routes are pleasant. .NET integration libraries usually leave you to Testcontainers.&lt;/p&gt;




&lt;h2&gt;
  
  
  Telemetry — OpenTelemetry built in, on by default
&lt;/h2&gt;

&lt;p&gt;Every step in every route emits an &lt;code&gt;Activity&lt;/code&gt; and a &lt;code&gt;Meter&lt;/code&gt; sample. No &lt;code&gt;AddInstrumentation&lt;/code&gt;, no per-step manual spans, no decorator wrapping. &lt;code&gt;EnableTelemetry&lt;/code&gt; and &lt;code&gt;EnableMetrics&lt;/code&gt; are &lt;code&gt;true&lt;/code&gt; out of the box — point your collector at the process and you immediately see per-route traces and per-step latency.&lt;/p&gt;

&lt;p&gt;For named sections that should show up as their own span or counter in Grafana / Jaeger / Tempo, the DSL has &lt;code&gt;Traced&lt;/code&gt; and &lt;code&gt;Metered&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="nf"&gt;From&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"kafka://orders"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Traced&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"order-processing"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;                   &lt;span class="c1"&gt;// one Activity for the whole block&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SetBody&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;JPath&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"$.order"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;Enrich&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;EndTraced&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Metered&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"order-throughput"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;                  &lt;span class="c1"&gt;// counter + histogram for the block&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;To&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"rabbitmq://processed"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;EndMetered&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="c1"&gt;// Inline form when the named span wraps a single step&lt;/span&gt;
&lt;span class="nf"&gt;From&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"kafka://orders"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Traced&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"validate"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;ValidateOrder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Metered&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"transform"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;In&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Body&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;Transform&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;To&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"rabbitmq://processed"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Standard metric names: &lt;code&gt;redb.route.messages.processed&lt;/code&gt;, &lt;code&gt;redb.route.messages.failed&lt;/code&gt;, &lt;code&gt;redb.route.processing.duration&lt;/code&gt; — emitted per route and per named step. They drop straight into any OTel-compatible backend.&lt;/p&gt;




&lt;h2&gt;
  
  
  What a real production route looks like
&lt;/h2&gt;

&lt;p&gt;The examples above are deliberately short. Real routes nest: HTTP listener → auth → permission check → method dispatch → business handler → audit tap. Indentation is the route hierarchy. The whole shape of the request is visible top-down:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;From  http://0.0.0.0:5090/api/.../settings   (inOut, cors)
  Process            Auth.ProcessAsync
  ConvertBody&amp;lt;string&amp;gt;
  Choice on Header redbHttp.Method
  ├─ POST
  │    RequirePermission   EditSettingsTables
  │    ProcessWithRedb     HandlePost
  │    WireTap → direct://audit   (onPrepare + newBodyFactory)
  ├─ DELETE
  │    RequirePermission   EditSettingsTables
  │    ProcessWithRedb     HandleDelete
  │    WireTap → direct://audit
  └─ else
       ProcessWithRedb     HandleGet
  EndChoice
Respond → HTTP caller
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is one route from the production system mentioned at the bottom of the post — settings API for a logistics admin panel:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Configure&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;From&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"http:0.0.0.0:5090/api/tsum/special-rc-settings?inOut=true&amp;amp;cors=true&amp;amp;corsOrigins=*"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;RouteId&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"tsum-api-special-rc-settings"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Auth&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ProcessAsync&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;                       &lt;span class="c1"&gt;// attach IPrincipal to exchange&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ConvertBody&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;()&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Choice&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;When&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;Header&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"redbHttp.Method"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;isEqualTo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"POST"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
                &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TsumAuthProcessor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;RequirePermission&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TsumPermission&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;EditSettingsTables&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
                &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ProcessWithRedb&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;redb&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;HandlePost&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;redb&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
                &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WireTap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"direct://tsum-audit"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="n"&gt;onPrepare&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;      &lt;span class="n"&gt;e&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;TsumAuditHelper&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SetHeaders&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"DATA_CHANGE"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                    &lt;span class="n"&gt;newBodyFactory&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;TsumAuditHelper&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;BuildDetailsJson&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;When&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;Header&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"redbHttp.Method"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;isEqualTo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"DELETE"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
                &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TsumAuthProcessor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;RequirePermission&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TsumPermission&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;EditSettingsTables&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
                &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ProcessWithRedb&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;redb&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;HandleDelete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;redb&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
                &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WireTap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"direct://tsum-audit"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="n"&gt;onPrepare&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;      &lt;span class="n"&gt;e&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;TsumAuditHelper&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SetHeaders&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"DATA_CHANGE"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                    &lt;span class="n"&gt;newBodyFactory&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;TsumAuditHelper&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;BuildDetailsJson&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Otherwise&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ProcessWithRedb&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;redb&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;HandleGet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;redb&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;EndChoice&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt; &lt;span class="nf"&gt;HandleGet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IRedbService&lt;/span&gt; &lt;span class="n"&gt;redb&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;IExchange&lt;/span&gt; &lt;span class="n"&gt;exchange&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;shippingPointId&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;ParseQueryLong&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;exchange&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"shippingPointId"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                       &lt;span class="p"&gt;??&lt;/span&gt; &lt;span class="nf"&gt;ParseQueryLong&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;exchange&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"rcId"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(!&lt;/span&gt;&lt;span class="n"&gt;shippingPointId&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HasValue&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;BadRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;exchange&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"shippingPointId required"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;items&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;redb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Query&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;SpecialRcSettings&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;()&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ShippingPoint&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="n"&gt;shippingPointId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToListAsync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="n"&gt;JsonRouteHelper&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SetJsonBody&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;exchange&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;items&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;MapToDto&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;OrderBy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CustomName&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToList&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two features worth pointing out:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;ProcessWithRedb((redb, ex, ct) =&amp;gt; ...)&lt;/code&gt;&lt;/strong&gt; — typed access to a named &lt;a href="https://github.com/redbase-app/redb" rel="noopener noreferrer"&gt;redb.Core&lt;/a&gt; service inside the pipeline. &lt;code&gt;redb.Query&amp;lt;SpecialRcSettings&amp;gt;().Where(...).ToListAsync()&lt;/code&gt; is just LINQ over a typed EAV scheme. No DbContext, no migrations, no separate repository class.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;WireTap("direct://tsum-audit", onPrepare: ..., newBodyFactory: ...)&lt;/code&gt;&lt;/strong&gt; — the audit hop runs in parallel with the main response, gets its own headers and its own body built fresh from the exchange. The HTTP client doesn't wait for audit, but audit sees the exact state of the exchange at that step.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is the actual shape of a real route. Method dispatch, auth, permission, business handler, audit — in one declarative block that reads top-down.&lt;/p&gt;




&lt;h2&gt;
  
  
  How it compares
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Apache Camel&lt;/th&gt;
&lt;th&gt;MassTransit&lt;/th&gt;
&lt;th&gt;NServiceBus&lt;/th&gt;
&lt;th&gt;Wolverine&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;redb.Route&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Language&lt;/td&gt;
&lt;td&gt;Java/JVM&lt;/td&gt;
&lt;td&gt;C#&lt;/td&gt;
&lt;td&gt;C#&lt;/td&gt;
&lt;td&gt;C#&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;C#&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Transports&lt;/td&gt;
&lt;td&gt;300+&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;22&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;EIP patterns (DSL)&lt;/td&gt;
&lt;td&gt;80+&lt;/td&gt;
&lt;td&gt;~5&lt;/td&gt;
&lt;td&gt;~5&lt;/td&gt;
&lt;td&gt;~5&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;30+&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Expression engine&lt;/td&gt;
&lt;td&gt;Interpreted&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Compiled&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Transactional pipelines across transports&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Bus only&lt;/td&gt;
&lt;td&gt;Bus only&lt;/td&gt;
&lt;td&gt;Bus only&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Yes&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Runtime container&lt;/td&gt;
&lt;td&gt;Karaf / Camel K&lt;/td&gt;
&lt;td&gt;Worker Service&lt;/td&gt;
&lt;td&gt;Worker Service&lt;/td&gt;
&lt;td&gt;Worker Service&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;redb.Tsak&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;License&lt;/td&gt;
&lt;td&gt;Apache 2.0&lt;/td&gt;
&lt;td&gt;Apache 2.0&lt;/td&gt;
&lt;td&gt;Commercial (&amp;gt;2 endpoints)&lt;/td&gt;
&lt;td&gt;MIT&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Apache 2.0&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;MassTransit, NServiceBus and Wolverine solve a different problem — they are &lt;strong&gt;message-bus frameworks&lt;/strong&gt; with handler discovery, durable sagas and managed outbox. redb.Route is a &lt;strong&gt;pipeline and transport integration engine&lt;/strong&gt;. They are not mutually exclusive: use redb.Route for cross-protocol routing and transformation, MassTransit for handler-style messaging and long-running saga state.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Deploying to production — redb.Tsak
&lt;/h2&gt;

&lt;p&gt;Writing &lt;code&gt;RouteBuilder&lt;/code&gt; classes is one thing. Running them in production across multiple nodes is another.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/redbase-app/redb-tsak" rel="noopener noreferrer"&gt;redb.Tsak&lt;/a&gt; is the runtime container built for redb.Route:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Drop a &lt;code&gt;.dll&lt;/code&gt; or &lt;code&gt;.tpkg&lt;/code&gt; (ZIP + manifest) into &lt;code&gt;Libs/&lt;/code&gt; — Tsak loads it without restart&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hot-reload&lt;/strong&gt; — update the file while running, zero downtime for other routes&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cluster mode&lt;/strong&gt; — leader election, automatic context redistribution across nodes. No ZooKeeper, no etcd. Coordination uses row locks in redb.Core.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;REST API&lt;/strong&gt; (32 endpoints), &lt;strong&gt;CLI&lt;/strong&gt; (30 commands), &lt;strong&gt;Blazor dashboard&lt;/strong&gt; with per-route metrics, logs, watchdog, cluster view&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You do not change a single line of &lt;code&gt;RouteBuilder&lt;/code&gt; code to go from &lt;code&gt;dotnet run&lt;/code&gt; to a 3-node production cluster.&lt;/p&gt;

&lt;p&gt;Full writeup on Tsak is coming in the next article. For now: &lt;a href="https://github.com/redbase-app/redb-tsak" rel="noopener noreferrer"&gt;github.com/redbase-app/redb-tsak&lt;/a&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Current state
&lt;/h2&gt;

&lt;p&gt;Running at &lt;a href="https://ews.ru" rel="noopener noreferrer"&gt;EWS&lt;/a&gt; — a 30-year-old national HoReCa food distributor:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;3-node cluster (4 cores / 8 GB / 50 GB SSD per node)&lt;/li&gt;
&lt;li&gt;~150k orders/month, ~3 months stable, 10–15% CPU under full load&lt;/li&gt;
&lt;li&gt;Active transports: SAP, Kafka, RabbitMQ, GPS feeds, Mercury / EGAIS / Chestny Znak / FGIS Grain&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;27 NuGet packages (core engine + 22 transports + 5 support libraries). Apache 2.0.&lt;/p&gt;

&lt;p&gt;It's not Apache Camel — not in transport count, not in maturity, not in ecosystem. But for the kind of integration work most .NET teams actually ship, it covers the ground a single library reasonably can. Honest feedback, missing transports, and breaking-case bug reports are all welcome.&lt;/p&gt;




&lt;h2&gt;
  
  
  Links
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;redb.Route&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/redbase-app/redb-route" rel="noopener noreferrer"&gt;github.com/redbase-app/redb-route&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;redb.Tsak&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/redbase-app/redb-tsak" rel="noopener noreferrer"&gt;github.com/redbase-app/redb-tsak&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Architecture&lt;/td&gt;
&lt;td&gt;&lt;a href="https://redbase.app/architecture" rel="noopener noreferrer"&gt;redbase.app/architecture&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Discussions&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/redbase-app/redb-route/discussions" rel="noopener noreferrer"&gt;github.com/redbase-app/redb-route/discussions&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Over to you
&lt;/h2&gt;

&lt;p&gt;This is exactly the moment when honest outside input is worth more than another internal sprint. A few specific things I'd love to hear:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Transports.&lt;/strong&gt; 22 cover most stacks I've seen, but obvious gaps remain. NATS / NATS JetStream? Pulsar? Service Bus topics with sessions? Google Pub/Sub? SQS+SNS as a pair? Salesforce Streaming API? OPC UA? Tell me what would make redb.Route a real fit for your stack.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;EIP patterns.&lt;/strong&gt; 30+ ship today. Anything from the Hohpe/Woolf catalogue you'd actually use and can't easily get elsewhere in .NET? Message Store with replay? Routing Slip? Process Manager? Normalizer? Format Indicator?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;DSL ergonomics.&lt;/strong&gt; Anything in the examples above that reads awkwardly in C#? Where would &lt;code&gt;[Source]&lt;/code&gt;/&lt;code&gt;[Sink]&lt;/code&gt; attributes, source generators, minimal-API-style &lt;code&gt;MapRoute("/orders")&lt;/code&gt;, or top-level statement DSL feel better than &lt;code&gt;RouteBuilder&lt;/code&gt; classes?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Observability.&lt;/strong&gt; OpenTelemetry is built in and on by default; Grafana dashboards and the live metrics / logs UI live in redb.Tsak (full writeup in the next article). What's still missing from your point of view — opinionated dashboard JSON, a turnkey OTel collector recipe, exemplars wired to trace IDs?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Migration.&lt;/strong&gt; If you have an existing Apache Camel route in production, would a side-by-side translation walkthrough (Camel Java → redb.Route C#) be useful? Which patterns hurt most?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;What stops you from trying it.&lt;/strong&gt; "License is good, but…" / "I like the DSL, but…" — the &lt;em&gt;but&lt;/em&gt; is the most valuable feedback I can get right now.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Drop a comment, open an issue, or start a thread in &lt;a href="https://github.com/redbase-app/redb-route/discussions" rel="noopener noreferrer"&gt;Discussions&lt;/a&gt;. Critical responses get the same priority as kind ones — both move the project forward.&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>csharp</category>
      <category>integration</category>
      <category>opensource</category>
    </item>
    <item>
      <title>I spent a year building Apache Camel for .NET. Here's the honest state of it.</title>
      <dc:creator>rinat kozin</dc:creator>
      <pubDate>Fri, 15 May 2026 16:23:44 +0000</pubDate>
      <link>https://dev.to/rinat_kozin_d0a2ef43e7824/i-spent-a-year-building-apache-camel-for-net-heres-the-honest-state-of-it-150e</link>
      <guid>https://dev.to/rinat_kozin_d0a2ef43e7824/i-spent-a-year-building-apache-camel-for-net-heres-the-honest-state-of-it-150e</guid>
      <description>&lt;p&gt;I've spent the last year building an integration ecosystem for .NET that I think fills a real gap. Three repos just went public. This post is the honest account — what it does, what it doesn't do yet, and three questions I genuinely don't know the answer to.&lt;/p&gt;




&lt;h2&gt;
  
  
  The gap
&lt;/h2&gt;

&lt;p&gt;If you need Apache Camel in .NET, you don't have it.&lt;/p&gt;

&lt;p&gt;MassTransit, Wolverine, NServiceBus are message buses — great at what they do, but they give you 4–7 transports and a Saga pattern. Apache Camel has 300+ components and 80+ EIP patterns as first-class DSL. But it's JVM.&lt;/p&gt;

&lt;p&gt;There's no .NET project that gives you the full EIP catalogue —&lt;br&gt;
Splitter, Aggregator, Content-Based Router, Recipient List, Dynamic Router,&lt;br&gt;
Wiretap, Dead Letter Channel — as a fluent C# DSL with 20+ transports.&lt;/p&gt;

&lt;p&gt;So I wrote one.&lt;/p&gt;


&lt;h2&gt;
  
  
  What I built
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;redb.Route&lt;/strong&gt; — Camel-style fluent C# DSL. Apache 2.0.&lt;br&gt;
&lt;a href="https://github.com/redbase-app/redb-route" rel="noopener noreferrer"&gt;github.com/redbase-app/redb-route&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;22 transports: Kafka, RabbitMQ, Redis, SQL polling, HTTP, gRPC, SFTP, MQTT,&lt;br&gt;
S3, IBM MQ, AMQP 1.0, Azure Service Bus, Elasticsearch, LDAP, Mail, TCP,&lt;br&gt;
WebSocket, SignalR, FTP, Quartz, File — plus 5 built-in (direct, seda, timer, log, mock).&lt;/p&gt;

&lt;p&gt;30+ EIP processors as first-class DSL steps. Compiled expression engine:&lt;br&gt;
&lt;code&gt;${header.price} * 1.2&lt;/code&gt; compiles to &lt;code&gt;Func&amp;lt;IExchange, decimal&amp;gt;&lt;/code&gt; via&lt;br&gt;
&lt;code&gt;System.Linq.Expressions&lt;/code&gt; at route-build time. No interpreter at runtime.&lt;/p&gt;



&lt;p&gt;&lt;strong&gt;redb.Tsak&lt;/strong&gt; — runtime container for redb.Route modules. Apache 2.0.&lt;br&gt;
&lt;a href="https://github.com/redbase-app/redb-tsak" rel="noopener noreferrer"&gt;github.com/redbase-app/redb-tsak&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Drop a &lt;code&gt;.dll&lt;/code&gt; or &lt;code&gt;.tpkg&lt;/code&gt; (ZIP + manifest) into a folder. Tsak picks it up,&lt;br&gt;
wires the DI container, starts the routes. Hot-reload — replace the file while&lt;br&gt;
running, zero downtime. REST API + CLI + Blazor dashboard.&lt;/p&gt;

&lt;p&gt;Cluster: leader election + auto-rebalance, no ZooKeeper/etcd/Consul.&lt;br&gt;
Coordination lives entirely in EAV (row locks + SELECT FOR UPDATE CAS + epoch fencing).&lt;/p&gt;

&lt;p&gt;Closest analogues are Apache Karaf and Camel K. Both JVM-only until now.&lt;/p&gt;



&lt;p&gt;&lt;strong&gt;redb.Core&lt;/strong&gt; — the storage layer underneath. Apache 2.0.&lt;br&gt;
&lt;a href="https://github.com/redbase-app/redb" rel="noopener noreferrer"&gt;github.com/redbase-app/redb&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;EAV-style typed object store over Postgres / MSSQL with full LINQ. Schema is a&lt;br&gt;
C# class with &lt;code&gt;[RedbScheme]&lt;/code&gt;. Values live in typed columns with FK constraints —&lt;br&gt;
not JSON blobs. &lt;code&gt;SyncSchemeAsync&amp;lt;T&amp;gt;()&lt;/code&gt; at startup, no migration files.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;RedbScheme&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Order"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;OrderProps&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;  &lt;span class="n"&gt;Status&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;decimal&lt;/span&gt; &lt;span class="n"&gt;Total&lt;/span&gt;  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Inside a route pipeline:&lt;/span&gt;
&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ProcessWithRedb&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"pg-main"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;redb&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;redb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SaveAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;RedbObject&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;OrderProps&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;name&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;$"Order-&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;DateTime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UtcNow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;HHmmss&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;Props&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;Status&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"new"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Total&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;299.99m&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;In&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Headers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"order-id"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;To&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"rabbitmq://processed"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Production state
&lt;/h2&gt;

&lt;p&gt;Running at &lt;a href="https://ews.ru" rel="noopener noreferrer"&gt;East-West / EWS&lt;/a&gt; — a 30-year-old national HoReCa&lt;br&gt;
food distributor in Russia.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;~150k orders/month, ~20k B2B customers, 600+ cities&lt;/li&gt;
&lt;li&gt;3-node cluster (4 cores / 8 GB / 50 GB SSD per node)&lt;/li&gt;
&lt;li&gt;~550 daily users, ~3 months stable, 10–15% CPU under full load&lt;/li&gt;
&lt;li&gt;Routes integrate: SAP, Kafka, RabbitMQ, GPS feeds, Mercury / EGAIS /
Chestny Znak / FGIS Grain (Russian regulatory systems)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;43 NuGet packages, ~8.4k total downloads. Real but small.&lt;/p&gt;




&lt;h2&gt;
  
  
  What's honest
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Bus factor of 1.&lt;/strong&gt; I'm the solo author of the framework code. Publishing&lt;br&gt;
to find early users and get harsh feedback.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pro tier exists.&lt;/strong&gt; Commercial license covers: compiled expression-tree-to-SQL,&lt;br&gt;
parallel materialization, change-tracking diff, schema migrations.&lt;br&gt;
Everything above — Route, Tsak, Core CRUD/LINQ/trees/security/export,&lt;br&gt;
22 transports, hot-reload, cluster — is Apache 2.0 and stays free.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;No third-party benchmarks yet.&lt;/strong&gt; Internal Free-vs-Pro numbers at&lt;br&gt;
&lt;a href="https://redbase.app/results" rel="noopener noreferrer"&gt;redbase.app/results&lt;/a&gt; — treat as&lt;br&gt;
"how editions compare on the same machine", not universal claims.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Architecture writeup:&lt;/strong&gt; &lt;a href="https://redbase.app/architecture" rel="noopener noreferrer"&gt;redbase.app/architecture&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;redb.Identity&lt;/strong&gt; (near-complete, not yet public) — OAuth 2.0 / OIDC server&lt;br&gt;
built on redb.Core. Authorization Code + PKCE, Client Credentials, Device Code,&lt;br&gt;
Refresh Token, PAR, DPoP, backchannel logout, private_key_jwt. Apache 2.0.&lt;/p&gt;




&lt;h2&gt;
  
  
  Three questions I don't have the answer to
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. Is "another integration framework" the wrong framing?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The gap I described feels real to me — I lived with it for years before building&lt;br&gt;
this. But maybe .NET teams that need Camel just use Camel via NativeAOT interop,&lt;br&gt;
or have given up on the EIP model entirely. Curious what you've actually done.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Does "EAV" as a term kill the pitch?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;redb.Core uses EAV — and that word triggers people (slow, untyped, joins&lt;br&gt;
everywhere). The design is typed columns + FK constraints + 40+ indexes, not&lt;br&gt;
JSON blobs. But maybe the term itself is a non-starter regardless of the&lt;br&gt;
implementation. Has anyone successfully reframed EAV to a skeptical audience?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. What makes a Pro/Free split feel fair vs bait-and-switch?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The free tier covers everything you need to build and run production pipelines.&lt;br&gt;
Pro adds performance features (compiled SQL, parallel materialization) and ops&lt;br&gt;
features (change tracking, migrations). Is that split legible from the outside,&lt;br&gt;
or does "compiled queries are Pro" read as "the free tier is intentionally crippled"?&lt;/p&gt;




&lt;h2&gt;
  
  
  Links
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;redb.Route&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/redbase-app/redb-route" rel="noopener noreferrer"&gt;github.com/redbase-app/redb-route&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;redb.Tsak&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/redbase-app/redb-tsak" rel="noopener noreferrer"&gt;github.com/redbase-app/redb-tsak&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;redb.Core&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/redbase-app/redb" rel="noopener noreferrer"&gt;github.com/redbase-app/redb&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Architecture&lt;/td&gt;
&lt;td&gt;&lt;a href="https://redbase.app/architecture" rel="noopener noreferrer"&gt;redbase.app/architecture&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Discussions&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/redbase-app/redb-route/discussions" rel="noopener noreferrer"&gt;github.com/redbase-app/redb-route/discussions&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Happy to answer technical questions in the comments.&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>csharp</category>
      <category>opensource</category>
      <category>discuss</category>
    </item>
    <item>
      <title>We built an enterprise integration stack for .NET from scratch: EAV + DSL + runtime</title>
      <dc:creator>rinat kozin</dc:creator>
      <pubDate>Wed, 13 May 2026 19:35:53 +0000</pubDate>
      <link>https://dev.to/rinat_kozin_d0a2ef43e7824/we-built-an-enterprise-integration-stack-for-net-from-scratch-eav-dsl-runtime-2l16</link>
      <guid>https://dev.to/rinat_kozin_d0a2ef43e7824/we-built-an-enterprise-integration-stack-for-net-from-scratch-eav-dsl-runtime-2l16</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F48m66mwccnu9yzl11ssr.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F48m66mwccnu9yzl11ssr.webp" alt="Three-layer stack diagram: redb.Tsak at top (hot-reload runtime and cluster coordination via EAV), redb.Route in the middle (22 transports, 30+ EIP patterns, compiled expressions, .ProcessWithRedb() writes to EAV), redb.Core at the bottom (typed EAV, full LINQ, Postgres and MSSQL). Arrows illustrate that Route reads and writes EAV directly, and Tsak stores all cluster state — leader election, distributed locks, node registry — in the same EAV database." width="800" height="800"&gt;&lt;/a&gt;&lt;br&gt;
Three open-source libraries. One coherent stack. All Apache 2.0.&lt;/p&gt;

&lt;p&gt;This is the story of what we built, why, and how the three pieces fit together&lt;br&gt;
in a way that surprised even us.&lt;/p&gt;


&lt;h2&gt;
  
  
  The problem
&lt;/h2&gt;

&lt;p&gt;Enterprise .NET integration in 2026 still looks like this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;EF Core for data — plus a migration file every time a field changes&lt;/li&gt;
&lt;li&gt;MassTransit or NServiceBus for messaging — great if you only need message brokers,
but IBM MQ, SFTP, MQTT, SQL polling? Custom adapters, every time&lt;/li&gt;
&lt;li&gt;Kubernetes + etcd for clustering — because "that's just how you do it"&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We needed all three. We built all three. And then we noticed they could share&lt;br&gt;
the same storage layer — and everything clicked.&lt;/p&gt;


&lt;h2&gt;
  
  
  Layer 1: redb.Core — typed EAV without the pain
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/redbase-app/redb" rel="noopener noreferrer"&gt;github.com/redbase-app/redb&lt;/a&gt;&lt;/strong&gt; · Apache 2.0&lt;/p&gt;


&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
    &lt;div class="c-embed__content"&gt;
        &lt;div class="c-embed__cover"&gt;
          &lt;a href="https://redbase.app/" class="c-link align-middle" rel="noopener noreferrer"&gt;
            &lt;img alt="" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fredbase.app%2Fimages%2Fog-home.png" height="" class="m-0" width=""&gt;
          &lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="c-embed__body"&gt;
        &lt;h2 class="fs-xl lh-tight"&gt;
          &lt;a href="https://redbase.app/" rel="noopener noreferrer" class="c-link"&gt;
            RedBase (REDB) - Entity Database for .NET | LINQ, Trees, Aggregations
          &lt;/a&gt;
        &lt;/h2&gt;
          &lt;p class="truncate-at-3"&gt;
            Not a key-value store. Real objects, real queries. LINQ, trees, aggregations — native SQL.
          &lt;/p&gt;
        &lt;div class="color-secondary fs-s flex items-center"&gt;
            &lt;img alt="favicon" class="c-embed__favicon m-0 mr-2 radius-0" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fredbase.app%2Ffavicon.ico" width="16" height="16"&gt;
          redbase.app
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;EAV (Entity–Attribute–Value) has a bad reputation — and for good reason.&lt;br&gt;
Raw EAV means losing types, losing LINQ, losing your mind.&lt;/p&gt;

&lt;p&gt;redb.Core is typed EAV. You define a schema as a plain C# class:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;RedbScheme&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Employee"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;EmployeeProps&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;  &lt;span class="n"&gt;Department&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;decimal&lt;/span&gt; &lt;span class="n"&gt;Salary&lt;/span&gt;     &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;     &lt;span class="n"&gt;Level&lt;/span&gt;      &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That attribute is the entire schema definition. No &lt;code&gt;DbContext&lt;/code&gt;. No &lt;code&gt;Add-Migration&lt;/code&gt;.&lt;br&gt;
No SQL files. Call &lt;code&gt;SyncSchemeAsync&amp;lt;EmployeeProps&amp;gt;()&lt;/code&gt; once at startup — done.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Save&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;redb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SaveAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;RedbObject&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;EmployeeProps&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Alice"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;Props&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;Department&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Engineering"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Salary&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;95000m&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Level&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// Query — full LINQ, compiled to SQL&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;seniors&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;redb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Query&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;EmployeeProps&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Level&lt;/span&gt; &lt;span class="p"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt; &lt;span class="p"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Salary&lt;/span&gt; &lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="m"&gt;80000m&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;OrderByDescending&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Salary&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToListAsync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;GroupBy, window functions, tree queries (CTE-based), aggregations — all there.&lt;br&gt;
Add a field to &lt;code&gt;EmployeeProps&lt;/code&gt; → call &lt;code&gt;SyncSchemeAsync&lt;/code&gt; → it's live. No migration.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why EAV?&lt;/strong&gt; Because the schema &lt;em&gt;is&lt;/em&gt; runtime data. You can add attributes, create&lt;br&gt;
new object types, and restructure relationships without touching the database schema.&lt;br&gt;
This matters a lot for the other two layers.&lt;/p&gt;


&lt;h2&gt;
  
  
  Layer 2: redb.Route — Apache Camel for .NET
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/redbase-app/redb-route" rel="noopener noreferrer"&gt;github.com/redbase-app/redb-route&lt;/a&gt;&lt;/strong&gt; · Apache 2.0&lt;/p&gt;

&lt;p&gt;If you know Apache Camel, you know the model: &lt;code&gt;From → Process → To&lt;/code&gt;.&lt;br&gt;
redb.Route brings that model to .NET — in pure C#, no XML, no JVM.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Kafka → filter → enrich → RabbitMQ&lt;/span&gt;
&lt;span class="nf"&gt;From&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"kafka://orders?groupId=svc&amp;amp;brokers=localhost:9092"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;Header&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;isEqualTo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"new"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Retry&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;To&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"rabbitmq://events?host=localhost"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;22 transports&lt;/strong&gt; out of the box: Kafka, RabbitMQ, IBM MQ, gRPC, HTTP, Redis,&lt;br&gt;
MQTT, S3, SFTP, FTP, SQL, TCP, WebSocket, SignalR, Azure Service Bus,&lt;br&gt;
Elasticsearch, Firebase, LDAP, Mail, AMQP 1.0, Quartz, File.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;30+ EIP patterns&lt;/strong&gt; as first-class DSL: Content-Based Router, Splitter,&lt;br&gt;
Aggregator, WireTap, Multicast, Recipient List, Dynamic Router, Resequencer,&lt;br&gt;
Scatter-Gather, Claim Check, Idempotent Consumer, Saga, Circuit Breaker,&lt;br&gt;
Throttle, Retry, Dead Letter, and more.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Compiled expression engine.&lt;/strong&gt; &lt;code&gt;${header.x}&lt;/code&gt;, &lt;code&gt;${header.x++}&lt;/code&gt;, arithmetic,&lt;br&gt;
JSONPath, XPath — all compiled to &lt;code&gt;Func&amp;lt;IExchange, T&amp;gt;&lt;/code&gt; via&lt;br&gt;
&lt;code&gt;System.Linq.Expressions&lt;/code&gt; at route-build time. No interpreter overhead,&lt;br&gt;
results cached per route.&lt;/p&gt;

&lt;p&gt;A real pipeline from production — HTTP entry → validate → dedup →&lt;br&gt;
Choice by priority → RabbitMQ RPC → AMQP RPC → gRPC RPC →&lt;br&gt;
SQL INSERT + SELECT → WireTap(Kafka, File) → response:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="nf"&gt;From&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"http:0.0.0.0:5088/api/demo?inOut=true"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ConvertBody&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Throttle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ValidateJsonSchema&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;messageSchema&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;IdempotentConsumer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;In&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetHeader&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="s"&gt;"traceId"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;repo&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Choice&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;When&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;Header&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"priority"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;isEqualTo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"high"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SetHeader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"fastTrack"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"true"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Otherwise&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SetHeader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"fastTrack"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"false"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;EndChoice&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;To&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"direct://pipeline"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  The integration point: Route reads and writes EAV
&lt;/h2&gt;

&lt;p&gt;This is where it gets interesting.&lt;/p&gt;

&lt;p&gt;The Route DSL has a &lt;code&gt;.ProcessWithRedb(...)&lt;/code&gt; step that gives you a named&lt;br&gt;
&lt;code&gt;IRedbService&lt;/code&gt; instance directly inside a pipeline step:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Inside a route — full EAV access without leaving the pipeline&lt;/span&gt;
&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ProcessWithRedb&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"pg-test"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;redb&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;exchange&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;redb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SaveAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;RedbObject&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;DemoItemProps&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;name&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;$"Demo-&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;DateTime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UtcNow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;HHmmss&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;Props&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;Title&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Pipeline result"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Priority&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;7&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="n"&gt;exchange&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;In&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Headers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"saved-id"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToString&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="c1"&gt;// Load what we just saved&lt;/span&gt;
&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ProcessWithRedb&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"pg-test"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;redb&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;exchange&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;long&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;exchange&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;In&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetHeader&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="s"&gt;"saved-id"&lt;/span&gt;&lt;span class="p"&gt;)!);&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;redb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;LoadAsync&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;DemoItemProps&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;exchange&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;In&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Headers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="n"&gt;Props&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="n"&gt;Title&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="c1"&gt;// LINQ query inside a pipeline step&lt;/span&gt;
&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ProcessWithRedb&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"pg-test"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;redb&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;exchange&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;results&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;redb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Query&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;DemoItemProps&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;()&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Priority&lt;/span&gt; &lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Take&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToListAsync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="n"&gt;exchange&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;In&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Headers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"count"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Count&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToString&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Found ${header.count} high-priority items"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The transport doesn't matter. Swap Kafka for IBM MQ for an HTTP webhook —&lt;br&gt;
the EAV read/write logic inside &lt;code&gt;.ProcessWithRedb(...)&lt;/code&gt; stays unchanged.&lt;/p&gt;


&lt;h2&gt;
  
  
  Layer 3: redb.Tsak — the runtime container
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/redbase-app/redb-tsak" rel="noopener noreferrer"&gt;github.com/redbase-app/redb-tsak&lt;/a&gt;&lt;/strong&gt; · Apache 2.0&lt;/p&gt;

&lt;p&gt;Drop a &lt;code&gt;.dll&lt;/code&gt; or a &lt;code&gt;.tpkg&lt;/code&gt; (ZIP with manifest + DLLs) into a folder.&lt;br&gt;
Tsak loads it, wires the DI container, starts the routes. Hot-reload:&lt;br&gt;
replace the file while running — zero downtime.&lt;/p&gt;

&lt;p&gt;REST API + CLI + Blazor dashboard for management.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The cluster has no ZooKeeper, no etcd, no Consul.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Leader election, distributed locks, and node registry live entirely in EAV:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;RedbLeaderElection
  → TTL-based row lock in EAV: "{groupName}:leader"
  → SELECT FOR UPDATE (atomic CAS)
  → epoch counter — stale leaders rejected by fencing

RedbDistributedLock
  → TryAcquireAsync: check existing lock → if expired → AtomicTakeover
  → AtomicTakeover: transaction + row-level lock, no races

RedbNodeRegistry
  → node records in EAV (TsakNodeProps)
  → registration protected by "{groupName}:node-register-lock"
  → serializes simultaneous startups
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Behind &lt;code&gt;ILeaderElection&lt;/code&gt; / &lt;code&gt;IDistributedLock&lt;/code&gt; / &lt;code&gt;INodeRegistry&lt;/code&gt; — swap in&lt;br&gt;
a K8s Lease implementation without changing anything else.&lt;/p&gt;

&lt;p&gt;The cluster &lt;em&gt;is&lt;/em&gt; the EAV database. No extra infrastructure.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why it fits together
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌─────────────────────────────────────────────────────┐
│                   redb.Tsak                         │
│  (hot-reload runtime, cluster, REST/CLI/Blazor UI)  │
│         cluster state stored in redb.Core EAV       │
├─────────────────────────────────────────────────────┤
│                  redb.Route                         │
│  (22 transports, 30+ EIP, compiled expressions)     │
│       .ProcessWithRedb() ── reads/writes EAV        │
├─────────────────────────────────────────────────────┤
│                  redb.Core                          │
│  (typed EAV, LINQ, no migrations, Postgres/MSSQL)   │
└─────────────────────────────────────────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;The &lt;strong&gt;storage layer&lt;/strong&gt; (EAV) is also the &lt;strong&gt;cluster coordination layer&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;pipeline engine&lt;/strong&gt; (Route) speaks directly to the &lt;strong&gt;data layer&lt;/strong&gt; (EAV)
without a separate service, separate DB, or separate connection&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;runtime&lt;/strong&gt; (Tsak) hosts Route assemblies with hot-reload and manages
cluster membership through the same EAV instance&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One PostgreSQL (or SQL Server) database. No Redis for caching, no ZooKeeper&lt;br&gt;
for coordination, no separate message store for pipeline state.&lt;/p&gt;




&lt;h2&gt;
  
  
  Numbers from production
&lt;/h2&gt;

&lt;p&gt;Running at &lt;a href="https://ews.ru" rel="noopener noreferrer"&gt;EWS&lt;/a&gt; — e-commerce platform:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;~550 daily active users, ~150,000 orders/month&lt;/li&gt;
&lt;li&gt;3-node Tsak cluster&lt;/li&gt;
&lt;li&gt;Pipelines: Kafka → filter/enrich → RabbitMQ, SQL outbox → HTTP webhooks,
SFTP polling → CSV → SQL upsert, Timer → SQL → Mail&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Zero-downtime deploys: replace &lt;code&gt;.tpkg&lt;/code&gt; → Tsak reloads the module → routes&lt;br&gt;
restart → in-flight messages complete on the old instance.&lt;/p&gt;




&lt;h2&gt;
  
  
  Links
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;redb.Core (EAV)&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/redbase-app/redb" rel="noopener noreferrer"&gt;github.com/redbase-app/redb&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;redb.Route (DSL)&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/redbase-app/redb-route" rel="noopener noreferrer"&gt;github.com/redbase-app/redb-route&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;redb.Tsak (runtime)&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/redbase-app/redb-tsak" rel="noopener noreferrer"&gt;github.com/redbase-app/redb-tsak&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;All Apache 2.0. Issues and PRs welcome.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Next in the series: deep dive into the compiled expression engine —&lt;br&gt;
how &lt;code&gt;${header.x++}&lt;/code&gt; becomes a &lt;code&gt;Func&amp;lt;IExchange, T&amp;gt;&lt;/code&gt; at build time.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>sql</category>
      <category>postgres</category>
      <category>architecture</category>
    </item>
  </channel>
</rss>
