<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>Ddd on Code Genos</title>
    <link>https://codegenos.github.io/tags/ddd/</link>
    <description>Recent content in Ddd on Code Genos</description>
    <image>
      <title>Code Genos</title>
      <url>https://codegenos.github.io/%3Clink%20or%20path%20of%20image%20for%20opengraph,%20twitter-cards%3E</url>
      <link>https://codegenos.github.io/%3Clink%20or%20path%20of%20image%20for%20opengraph,%20twitter-cards%3E</link>
    </image>
    <generator>Hugo</generator>
    <language>en-us</language>
    <lastBuildDate>Wed, 15 Jul 2026 17:14:00 +0300</lastBuildDate>
    <atom:link href="https://codegenos.github.io/tags/ddd/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>Concurrency Management with DDD in C#: Optimistic Locking, Versioning, and ETags</title>
      <link>https://codegenos.github.io/posts/concurrency-management-with-ddd-csharp/</link>
      <pubDate>Wed, 15 Jul 2026 17:14:00 +0300</pubDate>
      <guid>https://codegenos.github.io/posts/concurrency-management-with-ddd-csharp/</guid>
      <description>Learn optimistic concurrency in DDD with C#: EF Core RowVersion, aggregate versioning, Polly retries, and HTTP ETags. Production-ready code examples.</description>
      <content:encoded><![CDATA[<p>Concurrency is inevitable when multiple users or services change the same data. In <a href="https://learn.microsoft.com/en-us/dotnet/architecture/microservices/microservice-ddd-cqrs-patterns/domain-modeling-pros-cons">Domain-Driven Design</a>, we protect invariants inside an Aggregate boundary and use optimistic concurrency to avoid lost updates without resorting to database locks. This post shows practical C# patterns with EF Core and ASP.NET Core.</p>
<h2 id="key-ideas">Key ideas</h2>
<ul>
<li>Aggregates are the consistency boundary; only one transaction updates a single aggregate instance at a time.</li>
<li>Prefer optimistic concurrency: we detect conflicts and retry or surface a 409 Conflict.</li>
<li>Use a version (RowVersion/ETag) per aggregate for conflict detection.</li>
<li>Keep commands idempotent and side effects transactional (consider the Outbox pattern for integration events).</li>
</ul>
<h2 id="pattern-1--aggregate-versioning-with-ef-core-rowversion">Pattern 1 — Aggregate versioning with EF Core RowVersion</h2>
<p>RowVersion (a byte[] concurrency token) is a simple, reliable concurrency token that EF Core understands out of the box.</p>
<h3 id="how-rowversion-works-under-the-hood">How RowVersion Works Under the Hood</h3>
<p>RowVersion is <strong>not</strong> a timestamp despite the deprecated <code>TIMESTAMP</code> alias. It is an 8-byte binary value that SQL Server automatically increments whenever any column in the row changes.</p>
<table>
  <thead>
      <tr>
          <th>Property</th>
          <th>Description</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Storage</td>
          <td>8 bytes (<code>BINARY(8)</code>)</td>
      </tr>
      <tr>
          <td>Uniqueness</td>
          <td>Database-wide, not per table</td>
      </tr>
      <tr>
          <td>Generation</td>
          <td>Automatic on <code>INSERT</code> and <code>UPDATE</code></td>
      </tr>
      <tr>
          <td>Control</td>
          <td>SQL Server manages it; your application never sets it</td>
      </tr>
      <tr>
          <td>Checking current value</td>
          <td><code>SELECT @@DBTS</code> returns the database-wide timestamp counter</td>
      </tr>
  </tbody>
</table>
<p>Every database has a single monotonically increasing counter. Each time any row in any table is modified, SQL Server increments this counter and writes the new value into the row&rsquo;s <code>RowVersion</code> column. The value is not a date or time — it is a sequential binary number that tracks relative time within a database. SQL Server ignores any value you provide for <code>RowVersion</code> columns; the database assigns the value automatically.</p>
<p>When you call <code>SaveChanges()</code>, EF Core generates an UPDATE statement that includes the original <code>RowVersion</code> in the <code>WHERE</code> clause:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sql" data-lang="sql"><span class="line"><span class="cl"><span class="c1">-- EF Core generated SQL:
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="k">UPDATE</span><span class="w"> </span><span class="p">[</span><span class="n">Orders</span><span class="p">]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="k">SET</span><span class="w"> </span><span class="p">[</span><span class="n">Status</span><span class="p">]</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="o">@</span><span class="n">p0</span><span class="p">,</span><span class="w"> </span><span class="p">[</span><span class="n">CustomerId</span><span class="p">]</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="o">@</span><span class="n">p1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="k">WHERE</span><span class="w"> </span><span class="p">[</span><span class="n">Id</span><span class="p">]</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="o">@</span><span class="n">p2</span><span class="w"> </span><span class="k">AND</span><span class="w"> </span><span class="p">[</span><span class="n">RowVersion</span><span class="p">]</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="o">@</span><span class="n">p3</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="c1">-- If @@ROWCOUNT = 0, another session modified the row → conflict
</span></span></span><span class="line"><span class="cl"><span class="c1">-- If @@ROWCOUNT = 1, update succeeded
</span></span></span></code></pre></div><p>This is the core of optimistic concurrency: the database atomically checks whether the row has changed since it was read, and rejects the update if it has.</p>
<p><strong>PostgreSQL equivalent:</strong> PostgreSQL uses <code>xmin</code> (the transaction ID of the inserting/updating transaction) as its built-in concurrency token. This is a hidden system column so no extra column is needed in the table. You can map a <code>uint</code> property to the PostgreSQL <code>xmin</code> system column using the standard EF Core mechanisms:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="kd">public</span> <span class="k">class</span> <span class="nc">Order</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="kt">int</span> <span class="n">Id</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="kt">uint</span> <span class="n">Version</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="c1">// PostgreSQL exposes xmin through the standard concurrency token mechanism.</span>
</span></span><span class="line"><span class="cl"><span class="n">modelBuilder</span><span class="p">.</span><span class="n">Entity</span><span class="p">&lt;</span><span class="n">Order</span><span class="p">&gt;()</span>
</span></span><span class="line"><span class="cl">    <span class="p">.</span><span class="n">Property</span><span class="p">(</span><span class="n">o</span> <span class="p">=&gt;</span> <span class="n">o</span><span class="p">.</span><span class="n">Version</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">.</span><span class="n">IsRowVersion</span><span class="p">();</span>
</span></span></code></pre></div><p><strong>SQLite:</strong> SQLite has no native concurrency token type. You must manage a <code>Version</code> integer column entirely in application code or via triggers.</p>
<p><strong>Note:</strong> A <code>RowVersion</code> value changes even if the UPDATE sets a column to the same value it already holds. Any <code>UPDATE</code> statement at all increments the counter. Keep this in mind when writing bulk or no-op updates.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="c1">// Domain model (simplified)</span>
</span></span><span class="line"><span class="cl"><span class="kd">public</span> <span class="k">class</span> <span class="nc">Order</span> <span class="c1">// Aggregate Root</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="n">Guid</span> <span class="n">Id</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="kd">private</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="n">Guid</span> <span class="n">CustomerId</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="kd">private</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="kt">string</span> <span class="n">Status</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="kd">private</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span> <span class="p">=</span> <span class="s">&#34;Draft&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kd">private</span> <span class="k">readonly</span> <span class="n">List</span><span class="p">&lt;</span><span class="n">OrderLine</span><span class="p">&gt;</span> <span class="n">_lines</span> <span class="p">=</span> <span class="k">new</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="n">IReadOnlyCollection</span><span class="p">&lt;</span><span class="n">OrderLine</span><span class="p">&gt;</span> <span class="n">Lines</span> <span class="p">=&gt;</span> <span class="n">_lines</span><span class="p">.</span><span class="n">AsReadOnly</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// Concurrency token (EF Core)</span>
</span></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="kt">byte</span><span class="p">[]</span> <span class="n">RowVersion</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="kd">private</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span> <span class="p">=</span> <span class="n">Array</span><span class="p">.</span><span class="n">Empty</span><span class="p">&lt;</span><span class="kt">byte</span><span class="p">&gt;();</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="k">void</span> <span class="n">AddLine</span><span class="p">(</span><span class="n">Guid</span> <span class="n">productId</span><span class="p">,</span> <span class="kt">int</span> <span class="n">quantity</span><span class="p">,</span> <span class="kt">decimal</span> <span class="n">unitPrice</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="p">(</span><span class="n">quantity</span> <span class="p">&lt;=</span> <span class="m">0</span><span class="p">)</span> <span class="k">throw</span> <span class="k">new</span> <span class="n">ArgumentOutOfRangeException</span><span class="p">(</span><span class="n">nameof</span><span class="p">(</span><span class="n">quantity</span><span class="p">));</span>
</span></span><span class="line"><span class="cl">        <span class="n">_lines</span><span class="p">.</span><span class="n">Add</span><span class="p">(</span><span class="k">new</span> <span class="n">OrderLine</span><span class="p">(</span><span class="n">productId</span><span class="p">,</span> <span class="n">quantity</span><span class="p">,</span> <span class="n">unitPrice</span><span class="p">));</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="k">void</span> <span class="n">Submit</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="p">(!</span><span class="n">_lines</span><span class="p">.</span><span class="n">Any</span><span class="p">())</span> <span class="k">throw</span> <span class="k">new</span> <span class="n">InvalidOperationException</span><span class="p">(</span><span class="s">&#34;Cannot submit an empty order.&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="n">Status</span> <span class="p">=</span> <span class="s">&#34;Submitted&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kd">public</span> <span class="k">record</span> <span class="nc">OrderLine</span><span class="p">(</span><span class="n">Guid</span> <span class="n">ProductId</span><span class="p">,</span> <span class="kt">int</span> <span class="n">Quantity</span><span class="p">,</span> <span class="kt">decimal</span> <span class="n">UnitPrice</span><span class="p">);</span>
</span></span></code></pre></div><p>EF Core configuration with a RowVersion concurrency token:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="kd">public</span> <span class="kd">sealed</span> <span class="k">class</span> <span class="nc">OrderingDbContext</span> <span class="p">:</span> <span class="n">DbContext</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="n">DbSet</span><span class="p">&lt;</span><span class="n">Order</span><span class="p">&gt;</span> <span class="n">Orders</span> <span class="p">=&gt;</span> <span class="n">Set</span><span class="p">&lt;</span><span class="n">Order</span><span class="p">&gt;();</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="kd">protected</span> <span class="kd">override</span> <span class="k">void</span> <span class="n">OnModelCreating</span><span class="p">(</span><span class="n">ModelBuilder</span> <span class="n">modelBuilder</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="kt">var</span> <span class="n">order</span> <span class="p">=</span> <span class="n">modelBuilder</span><span class="p">.</span><span class="n">Entity</span><span class="p">&lt;</span><span class="n">Order</span><span class="p">&gt;();</span>
</span></span><span class="line"><span class="cl">        <span class="n">order</span><span class="p">.</span><span class="n">HasKey</span><span class="p">(</span><span class="n">o</span> <span class="p">=&gt;</span> <span class="n">o</span><span class="p">.</span><span class="n">Id</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="n">order</span><span class="p">.</span><span class="n">Property</span><span class="p">(</span><span class="n">o</span> <span class="p">=&gt;</span> <span class="n">o</span><span class="p">.</span><span class="n">RowVersion</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">             <span class="p">.</span><span class="n">IsRowVersion</span><span class="p">()</span> <span class="c1">// marks as concurrency token</span>
</span></span><span class="line"><span class="cl">             <span class="p">.</span><span class="n">IsRequired</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">        <span class="n">order</span><span class="p">.</span><span class="n">OwnsMany</span><span class="p">(</span><span class="n">o</span> <span class="p">=&gt;</span> <span class="n">o</span><span class="p">.</span><span class="n">Lines</span><span class="p">,</span> <span class="n">b</span> <span class="p">=&gt;</span>
</span></span><span class="line"><span class="cl">        <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="n">b</span><span class="p">.</span><span class="n">WithOwner</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">            <span class="n">b</span><span class="p">.</span><span class="n">Property</span><span class="p">&lt;</span><span class="kt">int</span><span class="p">&gt;(</span><span class="s">&#34;Id&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">            <span class="n">b</span><span class="p">.</span><span class="n">HasKey</span><span class="p">(</span><span class="s">&#34;Id&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="p">});</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>Repository save with conflict detection and a clean domain error:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="kd">public</span> <span class="k">class</span> <span class="nc">OrdersRepository</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kd">private</span> <span class="k">readonly</span> <span class="n">OrderingDbContext</span> <span class="n">_db</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="n">OrdersRepository</span><span class="p">(</span><span class="n">OrderingDbContext</span> <span class="n">db</span><span class="p">)</span> <span class="p">=&gt;</span> <span class="n">_db</span> <span class="p">=</span> <span class="n">db</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="kd">async</span> <span class="n">Task</span> <span class="n">SaveAsync</span><span class="p">(</span><span class="n">Order</span> <span class="n">order</span><span class="p">,</span> <span class="n">CancellationToken</span> <span class="n">ct</span> <span class="p">=</span> <span class="k">default</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="k">try</span>
</span></span><span class="line"><span class="cl">        <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="k">await</span> <span class="n">_db</span><span class="p">.</span><span class="n">SaveChangesAsync</span><span class="p">(</span><span class="n">ct</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">        <span class="k">catch</span> <span class="p">(</span><span class="n">DbUpdateConcurrencyException</span> <span class="n">ex</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="k">throw</span> <span class="k">new</span> <span class="n">ConcurrencyException</span><span class="p">(</span><span class="s">&#34;Order was modified by another process.&#34;</span><span class="p">,</span> <span class="n">ex</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kd">public</span> <span class="kd">sealed</span> <span class="k">class</span> <span class="nc">ConcurrencyException</span> <span class="p">:</span> <span class="n">Exception</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="n">ConcurrencyException</span><span class="p">(</span><span class="kt">string</span> <span class="n">message</span><span class="p">,</span> <span class="n">Exception</span><span class="p">?</span> <span class="n">inner</span> <span class="p">=</span> <span class="kc">null</span><span class="p">)</span> <span class="p">:</span> <span class="k">base</span><span class="p">(</span><span class="n">message</span><span class="p">,</span> <span class="n">inner</span><span class="p">)</span> <span class="p">{}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><h3 id="how-dbupdateconcurrencyexception-is-thrown">How DbUpdateConcurrencyException Is Thrown</h3>
<p>When <code>SaveChanges()</code> executes, EF Core checks the number of rows affected by each <code>UPDATE</code> or <code>DELETE</code> statement. If <strong>zero</strong> rows are affected for a tracked entity that has a concurrency token, EF Core throws <code>DbUpdateConcurrencyException</code>.</p>
<p>The generated SQL looks like this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sql" data-lang="sql"><span class="line"><span class="cl"><span class="c1">-- Step 1: UPDATE with concurrency check
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="k">SET</span><span class="w"> </span><span class="n">NOCOUNT</span><span class="w"> </span><span class="k">ON</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="k">UPDATE</span><span class="w"> </span><span class="p">[</span><span class="n">Orders</span><span class="p">]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="k">SET</span><span class="w"> </span><span class="p">[</span><span class="n">Status</span><span class="p">]</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="o">@</span><span class="n">p0</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="k">WHERE</span><span class="w"> </span><span class="p">[</span><span class="n">Id</span><span class="p">]</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="o">@</span><span class="n">p1</span><span class="w"> </span><span class="k">AND</span><span class="w"> </span><span class="p">[</span><span class="n">RowVersion</span><span class="p">]</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="o">@</span><span class="n">p2</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="c1">-- Step 2: Check affected rows
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="k">SELECT</span><span class="w"> </span><span class="p">[</span><span class="n">RowVersion</span><span class="p">]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="k">FROM</span><span class="w"> </span><span class="p">[</span><span class="n">Orders</span><span class="p">]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="k">WHERE</span><span class="w"> </span><span class="o">@@</span><span class="n">ROWCOUNT</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">1</span><span class="w"> </span><span class="k">AND</span><span class="w"> </span><span class="p">[</span><span class="n">Id</span><span class="p">]</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="o">@</span><span class="n">p1</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="c1">-- If @@ROWCOUNT = 0 → DbUpdateConcurrencyException
</span></span></span></code></pre></div><p>The exception&rsquo;s <code>Entries</code> property contains the affected entities. You can inspect them to retrieve the current database values and compare with the client&rsquo;s intended changes:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="k">catch</span> <span class="p">(</span><span class="n">DbUpdateConcurrencyException</span> <span class="n">ex</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">foreach</span> <span class="p">(</span><span class="kt">var</span> <span class="n">entry</span> <span class="k">in</span> <span class="n">ex</span><span class="p">.</span><span class="n">Entries</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="kt">var</span> <span class="n">clientValues</span> <span class="p">=</span> <span class="p">(</span><span class="n">Order</span><span class="p">)</span><span class="n">entry</span><span class="p">.</span><span class="n">Entity</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="kt">var</span> <span class="n">databaseValues</span> <span class="p">=</span> <span class="k">await</span> <span class="n">entry</span><span class="p">.</span><span class="n">GetDatabaseValuesAsync</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="p">(</span><span class="n">databaseValues</span> <span class="p">==</span> <span class="kc">null</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="c1">// Row was deleted by another user between read and write</span>
</span></span><span class="line"><span class="cl">            <span class="n">Console</span><span class="p">.</span><span class="n">WriteLine</span><span class="p">(</span><span class="s">&#34;The order was deleted by another user.&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">        <span class="k">else</span>
</span></span><span class="line"><span class="cl">        <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="c1">// Row was modified — show differences to the user</span>
</span></span><span class="line"><span class="cl">            <span class="kt">var</span> <span class="n">currentStatus</span> <span class="p">=</span> <span class="n">databaseValues</span><span class="p">.</span><span class="n">GetValue</span><span class="p">&lt;</span><span class="kt">string</span><span class="p">&gt;(</span><span class="n">nameof</span><span class="p">(</span><span class="n">Order</span><span class="p">.</span><span class="n">Status</span><span class="p">));</span>
</span></span><span class="line"><span class="cl">            <span class="kt">var</span> <span class="n">attemptedStatus</span> <span class="p">=</span> <span class="n">clientValues</span><span class="p">.</span><span class="n">Status</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">            <span class="n">Console</span><span class="p">.</span><span class="n">WriteLine</span><span class="p">(</span><span class="s">$&#34;Status was &#39;{currentStatus}&#39;, you attempted to set it to &#39;{attemptedStatus}&#39;&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p><strong>Important:</strong> After catching <code>DbUpdateConcurrencyException</code>, the EF Core change tracker&rsquo;s cached values are stale — the entity&rsquo;s <code>RowVersion</code> in the tracker still has the old value. Before retrying, you <strong>must</strong> either call <code>entry.Reload()</code>, detach the entity and re-query, or create a new <code>DbContext</code> instance. Retrying with the stale tracked entity will fail again because EF Core will use the same old <code>RowVersion</code> in the <code>WHERE</code> clause.</p>
<h2 id="pattern-2--retry-policy-on-concurrency-with-backoff">Pattern 2 — Retry policy on concurrency with backoff</h2>
<p>After detecting a conflict, reload the latest aggregate state, re-apply the command, and retry. Keep retries bounded and fast.</p>
<p><a href="https://www.pollydocs.org/">Polly</a> is the de-facto resilience library for .NET. It provides strategies for handling transient faults — <strong>Retry</strong>, <strong>Circuit Breaker</strong>, <strong>Timeout</strong>, <strong>Fallback</strong>, <strong>Rate Limiter</strong>, and <strong>Hedging</strong> — in a fluent, thread-safe API. For concurrency management, the two most relevant strategies are:</p>
<table>
  <thead>
      <tr>
          <th>Strategy</th>
          <th>Purpose</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><strong>Retry</strong></td>
          <td>Re-execute a failed operation after a delay. Use for transient conflicts (e.g., optimistic concurrency exceptions).</td>
      </tr>
      <tr>
          <td><strong>Circuit Breaker</strong></td>
          <td>Stop executing when failures exceed a threshold. Use to protect downstream systems from cascading failures.</td>
      </tr>
  </tbody>
</table>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="k">using</span> <span class="nn">Polly</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="k">using</span> <span class="nn">Polly.Retry</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kd">public</span> <span class="kd">sealed</span> <span class="k">class</span> <span class="nc">SubmitOrderHandler</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kd">private</span> <span class="k">readonly</span> <span class="n">OrdersRepository</span> <span class="n">_repo</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kd">private</span> <span class="k">readonly</span> <span class="n">OrderingDbContext</span> <span class="n">_db</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kd">private</span> <span class="kd">static</span> <span class="k">readonly</span> <span class="n">ResiliencePipeline</span> <span class="n">Retry</span> <span class="p">=</span> <span class="k">new</span> <span class="n">ResiliencePipelineBuilder</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">        <span class="p">.</span><span class="n">AddRetry</span><span class="p">(</span><span class="k">new</span> <span class="n">RetryStrategyOptions</span>
</span></span><span class="line"><span class="cl">        <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="n">MaxRetryAttempts</span> <span class="p">=</span> <span class="m">3</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="n">Delay</span> <span class="p">=</span> <span class="n">TimeSpan</span><span class="p">.</span><span class="n">FromMilliseconds</span><span class="p">(</span><span class="m">50</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">            <span class="n">BackoffType</span> <span class="p">=</span> <span class="n">DelayBackoffType</span><span class="p">.</span><span class="n">Exponential</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="n">ShouldHandle</span> <span class="p">=</span> <span class="k">new</span> <span class="n">PredicateBuilder</span><span class="p">().</span><span class="n">Handle</span><span class="p">&lt;</span><span class="n">ConcurrencyException</span><span class="p">&gt;(),</span>
</span></span><span class="line"><span class="cl">        <span class="p">})</span>
</span></span><span class="line"><span class="cl">        <span class="p">.</span><span class="n">Build</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="n">SubmitOrderHandler</span><span class="p">(</span><span class="n">OrdersRepository</span> <span class="n">repo</span><span class="p">,</span> <span class="n">OrderingDbContext</span> <span class="n">db</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">_repo</span> <span class="p">=</span> <span class="n">repo</span><span class="p">;</span> <span class="n">_db</span> <span class="p">=</span> <span class="n">db</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="n">Task</span> <span class="n">HandleAsync</span><span class="p">(</span><span class="n">Guid</span> <span class="n">orderId</span><span class="p">,</span> <span class="n">CancellationToken</span> <span class="n">ct</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="p">=&gt;</span> <span class="n">Retry</span><span class="p">.</span><span class="n">ExecuteAsync</span><span class="p">(</span><span class="kd">async</span> <span class="n">_</span> <span class="p">=&gt;</span>
</span></span><span class="line"><span class="cl">        <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="kt">var</span> <span class="n">order</span> <span class="p">=</span> <span class="k">await</span> <span class="n">_db</span><span class="p">.</span><span class="n">Orders</span><span class="p">.</span><span class="n">Include</span><span class="p">(</span><span class="n">o</span> <span class="p">=&gt;</span> <span class="n">o</span><span class="p">.</span><span class="n">Lines</span><span class="p">).</span><span class="n">FirstAsync</span><span class="p">(</span><span class="n">o</span> <span class="p">=&gt;</span> <span class="n">o</span><span class="p">.</span><span class="n">Id</span> <span class="p">==</span> <span class="n">orderId</span><span class="p">,</span> <span class="n">ct</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">            <span class="n">order</span><span class="p">.</span><span class="n">Submit</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">            <span class="k">await</span> <span class="n">_repo</span><span class="p">.</span><span class="n">SaveAsync</span><span class="p">(</span><span class="n">order</span><span class="p">,</span> <span class="n">ct</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="p">},</span> <span class="n">ct</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p><strong>Notes:</strong></p>
<ul>
<li>Do not retry unboundedly.</li>
<li>Commands must be idempotent or validated to avoid duplicate effects across retries.</li>
</ul>
<h3 id="why-wait-and-retry">Why Wait and Retry?</h3>
<p>When two transactions conflict, one succeeds and one fails. The failed transaction needs to reload the latest state and re-apply its changes. But it should not retry immediately — it should wait briefly so the winning transaction has time to fully commit and release any internal locks.</p>
<ol>
<li><strong>Transient failures are normal</strong> — In distributed systems, brief conflicts happen frequently and are usually resolved by the next attempt.</li>
<li><strong>Reduces contention</strong> — Waiting gives the conflicting transaction time to complete, so the retry encounters fresh data.</li>
<li><strong>Prevents thundering herd</strong> — Without waiting, all clients that failed at the same moment retry at the same time, creating a synchronized wave that can overwhelm the database.</li>
</ol>
<h3 id="exponential-backoff-with-jitter">Exponential Backoff with Jitter</h3>
<p>The retry policy uses exponential backoff: each retry waits longer than the previous one.</p>
<pre tabindex="0"><code>Attempt 1: Wait  50ms
Attempt 2: Wait 100ms
Attempt 3: Wait 200ms
</code></pre><p><strong>Jitter</strong> adds randomness to prevent synchronized retries. Without jitter, all clients that failed at time <code>T</code> retry at exactly <code>T + backoff</code>, creating a spike of correlated load.</p>
<table>
  <thead>
      <tr>
          <th>Jitter Type</th>
          <th>Formula</th>
          <th>Description</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Full Jitter</td>
          <td><code>random(0, base × 2^attempt)</code></td>
          <td>Spreads retries uniformly across the window. Recommended default.</td>
      </tr>
      <tr>
          <td>Equal Jitter</td>
          <td><code>(base × 2^attempt) / 2 + random(0, (base × 2^attempt) / 2)</code></td>
          <td>Guarantees a meaningful minimum wait. Good for payment APIs.</td>
      </tr>
      <tr>
          <td>Decorrelated</td>
          <td><code>random(base, prev_delay × 3)</code></td>
          <td>Widens spread on each retry. Useful under high concurrency.</td>
      </tr>
  </tbody>
</table>
<p>Full jitter is the simplest and most effective. AWS uses it as the default in most of its SDKs. The key idea: instead of all clients retrying at exactly 1s, 2s, 4s, and 8s, each client picks a random time within that window, spreading the load smoothly.</p>
<h3 id="do-not-retry-unboundedly">Do Not Retry Unboundedly</h3>
<p>Retrying infinitely is dangerous:</p>
<table>
  <thead>
      <tr>
          <th>Risk</th>
          <th>Description</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Resource exhaustion</td>
          <td>Memory, threads, and database connections are consumed by waiting requests</td>
      </tr>
      <tr>
          <td>Thundering herd amplification</td>
          <td>Retries add load to a system already struggling under contention</td>
      </tr>
      <tr>
          <td>Metastable failure</td>
          <td>The system cannot recover because retries keep the load high</td>
      </tr>
      <tr>
          <td>Poor user experience</td>
          <td>Requests hang indefinitely with no feedback</td>
      </tr>
  </tbody>
</table>
<p><strong>Best practice:</strong> Cap retries at 3–5 attempts, use circuit breakers for systemic failures, and fail fast when conflicts indicate a real business problem (e.g., a product is out of stock). For interactive requests, a total timeout of 5–10 seconds is reasonable; for background jobs, you can afford more.</p>
<h2 id="pattern-3--etags-for-http-write-concurrency">Pattern 3 — ETags for HTTP write concurrency</h2>
<p>Expose the aggregate RowVersion as an HTTP ETag. Require If-Match for updates to prevent lost writes.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="na">[ApiController]</span>
</span></span><span class="line"><span class="cl"><span class="na">[Route(&#34;api/orders&#34;)]</span>
</span></span><span class="line"><span class="cl"><span class="kd">public</span> <span class="k">class</span> <span class="nc">OrdersController</span> <span class="p">:</span> <span class="n">ControllerBase</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kd">private</span> <span class="k">readonly</span> <span class="n">OrderingDbContext</span> <span class="n">_db</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kd">private</span> <span class="k">readonly</span> <span class="n">OrdersRepository</span> <span class="n">_repo</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="n">OrdersController</span><span class="p">(</span><span class="n">OrderingDbContext</span> <span class="n">db</span><span class="p">,</span> <span class="n">OrdersRepository</span> <span class="n">repo</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span> 
</span></span><span class="line"><span class="cl">        <span class="n">_db</span> <span class="p">=</span> <span class="n">db</span><span class="p">;</span> 
</span></span><span class="line"><span class="cl">        <span class="n">_repo</span> <span class="p">=</span> <span class="n">repo</span><span class="p">;</span> 
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="na">
</span></span></span><span class="line"><span class="cl"><span class="na">    [HttpGet(&#34;{id}&#34;)]</span>
</span></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="kd">async</span> <span class="n">Task</span><span class="p">&lt;</span><span class="n">IActionResult</span><span class="p">&gt;</span> <span class="n">Get</span><span class="p">(</span><span class="n">Guid</span> <span class="n">id</span><span class="p">,</span> <span class="n">CancellationToken</span> <span class="n">ct</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="kt">var</span> <span class="n">order</span> <span class="p">=</span> <span class="k">await</span> <span class="n">_db</span><span class="p">.</span><span class="n">Orders</span><span class="p">.</span><span class="n">Include</span><span class="p">(</span><span class="n">o</span> <span class="p">=&gt;</span> <span class="n">o</span><span class="p">.</span><span class="n">Lines</span><span class="p">).</span><span class="n">FirstOrDefaultAsync</span><span class="p">(</span><span class="n">o</span> <span class="p">=&gt;</span> <span class="n">o</span><span class="p">.</span><span class="n">Id</span> <span class="p">==</span> <span class="n">id</span><span class="p">,</span> <span class="n">ct</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="p">(</span><span class="n">order</span> <span class="k">is</span> <span class="kc">null</span><span class="p">)</span> <span class="k">return</span> <span class="n">NotFound</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">        <span class="n">Response</span><span class="p">.</span><span class="n">Headers</span><span class="p">.</span><span class="n">ETag</span> <span class="p">=</span> <span class="s">$&#34;\&#34;</span><span class="p">{</span><span class="n">Convert</span><span class="p">.</span><span class="n">ToBase64String</span><span class="p">(</span><span class="n">order</span><span class="p">.</span><span class="n">RowVersion</span><span class="p">)}</span><span class="err">\</span><span class="s">&#34;&#34;</span><span class="p">;</span> <span class="c1">// quoted per RFC</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="n">Ok</span><span class="p">(</span><span class="n">order</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="na">
</span></span></span><span class="line"><span class="cl"><span class="na">    [HttpPut(&#34;{id}/submit&#34;)]</span>
</span></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="kd">async</span> <span class="n">Task</span><span class="p">&lt;</span><span class="n">IActionResult</span><span class="p">&gt;</span> <span class="n">Submit</span><span class="p">(</span><span class="n">Guid</span> <span class="n">id</span><span class="p">,</span> <span class="n">CancellationToken</span> <span class="n">ct</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="p">(!</span><span class="n">Request</span><span class="p">.</span><span class="n">Headers</span><span class="p">.</span><span class="n">TryGetValue</span><span class="p">(</span><span class="s">&#34;If-Match&#34;</span><span class="p">,</span> <span class="k">out</span> <span class="kt">var</span> <span class="n">etagHeader</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">            <span class="k">return</span> <span class="n">StatusCode</span><span class="p">(</span><span class="n">StatusCodes</span><span class="p">.</span><span class="n">Status428PreconditionRequired</span><span class="p">,</span> <span class="s">&#34;If-Match header required&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="kt">var</span> <span class="n">expected</span> <span class="p">=</span> <span class="n">ParseEtag</span><span class="p">(</span><span class="n">etagHeader</span><span class="p">!);</span>
</span></span><span class="line"><span class="cl">        <span class="kt">var</span> <span class="n">order</span> <span class="p">=</span> <span class="k">await</span> <span class="n">_db</span><span class="p">.</span><span class="n">Orders</span><span class="p">.</span><span class="n">Include</span><span class="p">(</span><span class="n">o</span> <span class="p">=&gt;</span> <span class="n">o</span><span class="p">.</span><span class="n">Lines</span><span class="p">).</span><span class="n">FirstOrDefaultAsync</span><span class="p">(</span><span class="n">o</span> <span class="p">=&gt;</span> <span class="n">o</span><span class="p">.</span><span class="n">Id</span> <span class="p">==</span> <span class="n">id</span><span class="p">,</span> <span class="n">ct</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="p">(</span><span class="n">order</span> <span class="k">is</span> <span class="kc">null</span><span class="p">)</span> <span class="k">return</span> <span class="n">NotFound</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="c1">// Compare expected RowVersion with current</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="p">(!</span><span class="n">order</span><span class="p">.</span><span class="n">RowVersion</span><span class="p">.</span><span class="n">AsSpan</span><span class="p">().</span><span class="n">SequenceEqual</span><span class="p">(</span><span class="n">expected</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">            <span class="k">return</span> <span class="n">StatusCode</span><span class="p">(</span><span class="n">StatusCodes</span><span class="p">.</span><span class="n">Status412PreconditionFailed</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="n">order</span><span class="p">.</span><span class="n">Submit</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">        <span class="k">try</span>
</span></span><span class="line"><span class="cl">        <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="k">await</span> <span class="n">_repo</span><span class="p">.</span><span class="n">SaveAsync</span><span class="p">(</span><span class="n">order</span><span class="p">,</span> <span class="n">ct</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">            <span class="k">return</span> <span class="n">NoContent</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">        <span class="k">catch</span> <span class="p">(</span><span class="n">ConcurrencyException</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="k">return</span> <span class="n">Conflict</span><span class="p">();</span> <span class="c1">// 409</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="kd">static</span> <span class="kt">byte</span><span class="p">[]</span> <span class="n">ParseEtag</span><span class="p">(</span><span class="kt">string</span> <span class="n">etag</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="kt">var</span> <span class="n">trimmed</span> <span class="p">=</span> <span class="n">etag</span><span class="p">.</span><span class="n">Trim</span><span class="p">(</span><span class="sc">&#39;&#34;&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">            <span class="k">return</span> <span class="n">Convert</span><span class="p">.</span><span class="n">FromBase64String</span><span class="p">(</span><span class="n">trimmed</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><h3 id="why-require-if-match">Why Require If-Match?</h3>
<p>Without <code>If-Match</code>, clients can silently overwrite each other&rsquo;s changes (the &ldquo;lost update&rdquo; problem described in RFC 7232):</p>
<pre tabindex="0"><code>Timeline WITHOUT If-Match (lost update):
1. Client A reads Order (RowVersion = 1)
2. Client B reads Order (RowVersion = 1)
3. Client A updates status → succeeds (RowVersion becomes 2)
4. Client B updates price  → also succeeds (overwrites Client A&#39;s status change!)
   Client A&#39;s change is silently lost.
</code></pre><p>With <code>If-Match</code>, the server rejects stale updates before any data is written:</p>
<pre tabindex="0"><code>Timeline WITH If-Match:
1. Client A reads Order (RowVersion = 1), receives ETag &#34;v1&#34;
2. Client B reads Order (RowVersion = 1), receives ETag &#34;v1&#34;
3. Client A updates with If-Match: &#34;v1&#34; → succeeds (RowVersion becomes 2)
4. Client B updates with If-Match: &#34;v1&#34; → 412 Precondition Failed
   Client B is notified and can refetch, merge, and retry.
</code></pre><h3 id="http-status-codes-for-concurrency">HTTP Status Codes for Concurrency</h3>
<table>
  <thead>
      <tr>
          <th>Status Code</th>
          <th>Name</th>
          <th>When to Return</th>
          <th>Client Action</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><strong>428</strong></td>
          <td>Precondition Required</td>
          <td>Client did not send an <code>If-Match</code> header</td>
          <td><code>GET</code> the resource, obtain the current ETag, retry the request with <code>If-Match</code></td>
      </tr>
      <tr>
          <td><strong>412</strong></td>
          <td>Precondition Failed</td>
          <td>The ETag in <code>If-Match</code> does not match the current resource version</td>
          <td><code>GET</code> the resource again, see what changed, decide whether to merge or overwrite, retry with fresh ETag</td>
      </tr>
      <tr>
          <td><strong>409</strong></td>
          <td>Conflict</td>
          <td>A business-logic conflict occurred (e.g., invalid state transition, duplicate entry) independent of conditional headers</td>
          <td>Resolve the domain conflict, then retry if still valid</td>
      </tr>
  </tbody>
</table>
<p><strong>428 vs 412:</strong> The 428 tells the client &ldquo;you forgot to participate in optimistic concurrency&rdquo; and teaches it the read-then-write pattern. The 412 tells the client &ldquo;your version is stale — someone else edited this resource since you last read it.&rdquo; An API that fully implements optimistic locking uses both together: new or naive clients learn the pattern from 428, while well-behaved clients get 412 when a race occurs.</p>
<p><strong>412 vs 409:</strong> The 412 is tied to explicit HTTP precondition headers (<code>If-Match</code>, <code>If-Unmodified-Since</code>). The 409 is a broader signal for application-level conflicts that are not expressed as conditional headers. Use 412 for version/stale-ETag failures. Use 409 for business-rule violations (e.g., cannot cancel a shipped order).</p>
<h3 id="rfc-references">RFC References</h3>
<table>
  <thead>
      <tr>
          <th>RFC</th>
          <th>Defines</th>
          <th>Relevance</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><a href="https://httpwg.org/specs/rfc6585.html">RFC 6585</a></td>
          <td><code>428 Precondition Required</code></td>
          <td>&ldquo;Its typical use is to avoid the &rsquo;lost update&rsquo; problem&rdquo; — server requires the request to be conditional</td>
      </tr>
      <tr>
          <td><a href="https://datatracker.ietf.org/doc/html/rfc7232">RFC 7232</a></td>
          <td><code>If-Match</code> header</td>
          <td>Conditional requests for optimistic concurrency; strong ETag comparison required</td>
      </tr>
      <tr>
          <td><a href="https://datatracker.ietf.org/doc/html/rfc9110#section-15.5.13">RFC 9110</a></td>
          <td><code>412 Precondition Failed</code></td>
          <td>&ldquo;One or more conditions given in the request header fields evaluated to false&rdquo;</td>
      </tr>
  </tbody>
</table>
<h3 id="client-side-response-guide">Client-Side Response Guide</h3>
<p>When a client receives a concurrency status code, it should follow this decision tree:</p>
<pre tabindex="0"><code>Client sends PUT/PATCH with If-Match
│
├─ 2xx Success → Update local ETag from response header
│
├─ 428 Precondition Required
│  → Client forgot If-Match header
│  → GET the resource, store ETag, retry with If-Match
│
├─ 412 Precondition Failed
│  → ETag is stale (another client updated the resource)
│  → GET the resource, compare local changes with server state
│  ├─ Changes are compatible → merge and retry with new ETag
│  └─ Changes conflict → present conflict to user for resolution
│
├─ 409 Conflict
│  → Business-level conflict (not a version mismatch)
│  → Read error body for details, resolve domain issue, retry if valid
│
└─ 5xx Server Error
   → Transient failure
   → Retry with exponential backoff
</code></pre><h2 id="pattern-4--using-an-integer-version-instead-of-rowversion">Pattern 4 — Using an integer Version instead of RowVersion</h2>
<p>If you prefer an integer, configure it as a concurrency token. EF Core will include it in the WHERE clause when updating.</p>
<p><strong>Data Annotations:</strong></p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="kd">public</span> <span class="k">class</span> <span class="nc">Product</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="n">Guid</span> <span class="n">Id</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="kd">private</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="kt">string</span> <span class="n">Name</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="kd">private</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span> <span class="p">=</span> <span class="kt">string</span><span class="p">.</span><span class="n">Empty</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="na">
</span></span></span><span class="line"><span class="cl"><span class="na">    [ConcurrencyCheck]</span>
</span></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="kt">int</span> <span class="n">Version</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="kd">private</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="k">void</span> <span class="n">Rename</span><span class="p">(</span><span class="kt">string</span> <span class="n">name</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">Name</span> <span class="p">=</span> <span class="n">name</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p><strong>Fluent API:</strong></p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="kd">protected</span> <span class="kd">override</span> <span class="k">void</span> <span class="n">OnModelCreating</span><span class="p">(</span><span class="n">ModelBuilder</span> <span class="n">modelBuilder</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">modelBuilder</span><span class="p">.</span><span class="n">Entity</span><span class="p">&lt;</span><span class="n">Product</span><span class="p">&gt;()</span>
</span></span><span class="line"><span class="cl">        <span class="p">.</span><span class="n">Property</span><span class="p">(</span><span class="n">p</span> <span class="p">=&gt;</span> <span class="n">p</span><span class="p">.</span><span class="n">Version</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="p">.</span><span class="n">IsConcurrencyToken</span><span class="p">();</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>On save, EF Core throws DbUpdateConcurrencyException if another writer has already incremented Version. You can increment Version in the database via a trigger, or let EF compute it by setting StoreGeneratedPattern where appropriate.</p>
<h3 id="isconcurrencytoken-vs-isrowversion">IsConcurrencyToken() vs. IsRowVersion()</h3>
<p>While both handle optimistic concurrency, they serve different scenarios:</p>
<table>
  <thead>
      <tr>
          <th>Feature</th>
          <th>IsConcurrencyToken()</th>
          <th>IsRowVersion()</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Who updates it?</td>
          <td><strong>Your application</strong> or a <strong>DB trigger</strong> must change the value on every write.</td>
          <td>The <strong>database engine</strong> increments or changes it automatically.</td>
      </tr>
      <tr>
          <td>Data Type</td>
          <td>Any supported type (e.g., <code>int</code>, <code>string</code>, <code>Guid</code>).</td>
          <td>A <code>byte[]</code> array or specialized database type.</td>
      </tr>
      <tr>
          <td>Scope</td>
          <td>Only checks the specific configured column(s).</td>
          <td>Tracks changes to any column in that entire row.</td>
      </tr>
      <tr>
          <td>Provider Support</td>
          <td>Works across all databases (SQL Server, PostgreSQL, SQLite, etc.).</td>
          <td>Heavily dependent on native DB engine support (e.g., SQL Server rowversion).</td>
      </tr>
  </tbody>
</table>
<h3 id="how-the-integer-version-mechanism-works">How the Integer Version Mechanism Works</h3>
<p>Unlike <code>RowVersion</code> (which SQL Server auto-generates and manages), integer versions require you to manage the increment yourself. There are two approaches:</p>
<h4 id="option-1-database-trigger">Option 1: Database Trigger</h4>
<p>The database increments the version on every UPDATE, transparent to the application:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sql" data-lang="sql"><span class="line"><span class="cl"><span class="k">CREATE</span><span class="w"> </span><span class="k">TRIGGER</span><span class="w"> </span><span class="p">[</span><span class="n">dbo</span><span class="p">].[</span><span class="n">Products_VERSION</span><span class="p">]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="k">ON</span><span class="w"> </span><span class="p">[</span><span class="n">dbo</span><span class="p">].[</span><span class="n">Products</span><span class="p">]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="k">AFTER</span><span class="w"> </span><span class="k">UPDATE</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="k">AS</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="k">BEGIN</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">SET</span><span class="w"> </span><span class="n">NOCOUNT</span><span class="w"> </span><span class="k">ON</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">UPDATE</span><span class="w"> </span><span class="n">p</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">SET</span><span class="w"> </span><span class="n">p</span><span class="p">.</span><span class="k">Version</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">p</span><span class="p">.</span><span class="k">Version</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="mi">1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">FROM</span><span class="w"> </span><span class="p">[</span><span class="n">dbo</span><span class="p">].[</span><span class="n">Products</span><span class="p">]</span><span class="w"> </span><span class="n">p</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">INNER</span><span class="w"> </span><span class="k">JOIN</span><span class="w"> </span><span class="n">INSERTED</span><span class="w"> </span><span class="n">i</span><span class="w"> </span><span class="k">ON</span><span class="w"> </span><span class="n">p</span><span class="p">.</span><span class="n">Id</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">i</span><span class="p">.</span><span class="n">Id</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="k">END</span><span class="w">
</span></span></span></code></pre></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sql" data-lang="sql"><span class="line"><span class="cl"><span class="c1">-- PostgreSQL equivalent
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="k">CREATE</span><span class="w"> </span><span class="k">OR</span><span class="w"> </span><span class="k">REPLACE</span><span class="w"> </span><span class="k">FUNCTION</span><span class="w"> </span><span class="n">update_version</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="k">RETURNS</span><span class="w"> </span><span class="k">TRIGGER</span><span class="w"> </span><span class="k">AS</span><span class="w"> </span><span class="err">$$</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="k">BEGIN</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">NEW</span><span class="p">.</span><span class="k">version</span><span class="w"> </span><span class="p">:</span><span class="o">=</span><span class="w"> </span><span class="k">OLD</span><span class="p">.</span><span class="k">version</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="mi">1</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">RETURN</span><span class="w"> </span><span class="k">NEW</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="k">END</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="err">$$</span><span class="w"> </span><span class="k">LANGUAGE</span><span class="w"> </span><span class="n">plpgsql</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="k">CREATE</span><span class="w"> </span><span class="k">TRIGGER</span><span class="w"> </span><span class="n">products_version_trigger</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">BEFORE</span><span class="w"> </span><span class="k">UPDATE</span><span class="w"> </span><span class="k">ON</span><span class="w"> </span><span class="n">products</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">FOR</span><span class="w"> </span><span class="k">EACH</span><span class="w"> </span><span class="k">ROW</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">EXECUTE</span><span class="w"> </span><span class="k">FUNCTION</span><span class="w"> </span><span class="n">update_version</span><span class="p">();</span><span class="w">
</span></span></span></code></pre></div><p><strong>Data Annotations:</strong></p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="c1">// Data Annotations equivalent:</span>
</span></span><span class="line"><span class="cl"><span class="c1">// [ConcurrencyCheck] marks the property as a concurrency token</span>
</span></span><span class="line"><span class="cl"><span class="c1">// [DatabaseGenerated(Computed)] tells EF Core the DB generates the value on INSERT and UPDATE</span>
</span></span><span class="line"><span class="cl"><span class="kd">public</span> <span class="k">class</span> <span class="nc">Product</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="n">Guid</span> <span class="n">Id</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="kd">private</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="kt">string</span> <span class="n">Name</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="kd">private</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span> <span class="p">=</span> <span class="kt">string</span><span class="p">.</span><span class="n">Empty</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="na">
</span></span></span><span class="line"><span class="cl"><span class="na">    [ConcurrencyCheck]</span>
</span></span><span class="line"><span class="cl"><span class="na">    [DatabaseGenerated(DatabaseGeneratedOption.Computed)]</span>
</span></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="kt">int</span> <span class="n">Version</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="kd">private</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p><strong>Fluent API:</strong></p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="c1">// Fluent API equivalent — ValueGeneratedOnAddOrUpdate tells EF Core</span>
</span></span><span class="line"><span class="cl"><span class="c1">// the database controls value generation (trigger)</span>
</span></span><span class="line"><span class="cl"><span class="kd">protected</span> <span class="kd">override</span> <span class="k">void</span> <span class="n">OnModelCreating</span><span class="p">(</span><span class="n">ModelBuilder</span> <span class="n">modelBuilder</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">modelBuilder</span><span class="p">.</span><span class="n">Entity</span><span class="p">&lt;</span><span class="n">Product</span><span class="p">&gt;()</span>
</span></span><span class="line"><span class="cl">        <span class="p">.</span><span class="n">Property</span><span class="p">(</span><span class="n">p</span> <span class="p">=&gt;</span> <span class="n">p</span><span class="p">.</span><span class="n">Version</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="p">.</span><span class="n">IsConcurrencyToken</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">        <span class="p">.</span><span class="n">ValueGeneratedOnAddOrUpdate</span><span class="p">();</span> <span class="c1">// DB handles value generation (trigger)</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>With the trigger approach, EF Core excludes <code>Version</code> from INSERT and UPDATE statements — the trigger handles the increment. <code>[DatabaseGenerated(Computed)]</code> and <code>.ValueGeneratedOnAddOrUpdate()</code> are equivalent; choose whichever fits your style. This configuration tells the framework:</p>
<p><em>The database will automatically generate or update the value of this column whenever a row is inserted or updated. Do not try to write to this column; instead, read the new value back from the database after saving.</em></p>
<h4 id="option-2-application-code-c-increment">Option 2: Application Code (C# Increment)</h4>
<p>Increment the version in application code using a <code>SaveChangesInterceptor</code>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="c1">// Marker interface — entities with application-managed version</span>
</span></span><span class="line"><span class="cl"><span class="kd">public</span> <span class="k">interface</span> <span class="nc">IVersionedEntity</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kt">int</span> <span class="n">Version</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">// Domain entity implements the interface</span>
</span></span><span class="line"><span class="cl"><span class="kd">public</span> <span class="k">class</span> <span class="nc">Product</span> <span class="p">:</span> <span class="n">IVersionedEntity</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="n">Guid</span> <span class="n">Id</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="kd">private</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="kt">string</span> <span class="n">Name</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="kd">private</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span> <span class="p">=</span> <span class="kt">string</span><span class="p">.</span><span class="n">Empty</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="na">
</span></span></span><span class="line"><span class="cl"><span class="na">    [ConcurrencyCheck]</span> <span class="c1">// marks as concurrency token via Data Annotations</span>
</span></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="kt">int</span> <span class="n">Version</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span> <span class="c1">// public setter for interceptor</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="k">void</span> <span class="n">Rename</span><span class="p">(</span><span class="kt">string</span> <span class="n">name</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">Name</span> <span class="p">=</span> <span class="n">name</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="c1">// Fluent API equivalent — no ValueGeneratedOnAddOrUpdate because</span>
</span></span><span class="line"><span class="cl"><span class="c1">// the application controls the value</span>
</span></span><span class="line"><span class="cl"><span class="kd">protected</span> <span class="kd">override</span> <span class="k">void</span> <span class="n">OnModelCreating</span><span class="p">(</span><span class="n">ModelBuilder</span> <span class="n">modelBuilder</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">modelBuilder</span><span class="p">.</span><span class="n">Entity</span><span class="p">&lt;</span><span class="n">Product</span><span class="p">&gt;()</span>
</span></span><span class="line"><span class="cl">        <span class="p">.</span><span class="n">Property</span><span class="p">(</span><span class="n">p</span> <span class="p">=&gt;</span> <span class="n">p</span><span class="p">.</span><span class="n">Version</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="p">.</span><span class="n">IsConcurrencyToken</span><span class="p">();</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>With the application code approach, <code>[ConcurrencyCheck]</code> and <code>.IsConcurrencyToken()</code> are equivalent. We do <strong>not</strong> use <code>[DatabaseGenerated(Computed)]</code> or <code>.ValueGeneratedOnAddOrUpdate()</code> because the application is responsible for incrementing the value — EF Core must include <code>Version</code> in the UPDATE statement.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="c1">// Interceptor — increments Version on every modified entity before save</span>
</span></span><span class="line"><span class="cl"><span class="kd">public</span> <span class="kd">sealed</span> <span class="k">class</span> <span class="nc">ConcurrencyVersionInterceptor</span> <span class="p">:</span> <span class="n">SaveChangesInterceptor</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="kd">override</span> <span class="n">ValueTask</span><span class="p">&lt;</span><span class="n">InterceptionResult</span><span class="p">&lt;</span><span class="kt">int</span><span class="p">&gt;&gt;</span> <span class="n">SavingChangesAsync</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="n">DbContextEventData</span> <span class="n">eventData</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">InterceptionResult</span><span class="p">&lt;</span><span class="kt">int</span><span class="p">&gt;</span> <span class="n">result</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">CancellationToken</span> <span class="n">cancellationToken</span> <span class="p">=</span> <span class="k">default</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="p">(</span><span class="n">eventData</span><span class="p">.</span><span class="n">Context</span> <span class="k">is</span> <span class="kc">null</span><span class="p">)</span> <span class="k">return</span> <span class="n">ValueTask</span><span class="p">.</span><span class="n">FromResult</span><span class="p">(</span><span class="n">result</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="k">foreach</span> <span class="p">(</span><span class="kt">var</span> <span class="n">entry</span> <span class="k">in</span> <span class="n">eventData</span><span class="p">.</span><span class="n">Context</span><span class="p">.</span><span class="n">ChangeTracker</span><span class="p">.</span><span class="n">Entries</span><span class="p">&lt;</span><span class="n">IVersionedEntity</span><span class="p">&gt;())</span>
</span></span><span class="line"><span class="cl">        <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="k">if</span> <span class="p">(</span><span class="n">entry</span><span class="p">.</span><span class="n">State</span> <span class="p">==</span> <span class="n">EntityState</span><span class="p">.</span><span class="n">Modified</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">                <span class="n">entry</span><span class="p">.</span><span class="n">Entity</span><span class="p">.</span><span class="n">Version</span><span class="p">++;</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="n">ValueTask</span><span class="p">.</span><span class="n">FromResult</span><span class="p">(</span><span class="n">result</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">// Register in DbContext</span>
</span></span><span class="line"><span class="cl"><span class="n">dbContext</span><span class="p">.</span><span class="n">AddInterceptor</span><span class="p">(</span><span class="k">new</span> <span class="n">ConcurrencyVersionInterceptor</span><span class="p">());</span>
</span></span></code></pre></div><p>The interceptor runs before <code>SaveChanges</code> and increments <code>Version</code> on every modified entity. No database triggers required — version management stays entirely in the application layer.</p>
<h3 id="concurrencycheck-attribute-explained">[ConcurrencyCheck] Attribute Explained</h3>
<p>The <code>[ConcurrencyCheck]</code> attribute is the Data Annotations equivalent of Fluent API&rsquo;s <code>.IsConcurrencyToken()</code>. It tells EF Core to include this property in the <code>WHERE</code> clause of <code>UPDATE</code> and <code>DELETE</code> statements.</p>
<table>
  <thead>
      <tr>
          <th>Approach</th>
          <th>Syntax</th>
          <th>When to Use</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><code>[ConcurrencyCheck]</code></td>
          <td><code>[ConcurrencyCheck] public int Version { get; set; }</code></td>
          <td>Quick setup; attribute lives on the domain entity</td>
      </tr>
      <tr>
          <td>Fluent API</td>
          <td><code>.Property(p =&gt; p.Version).IsConcurrencyToken()</code></td>
          <td>Full control; infrastructure concern stays in the persistence layer</td>
      </tr>
  </tbody>
</table>
<p><strong>Key difference from RowVersion:</strong> Properties decorated with <code>[ConcurrencyCheck]</code> are <strong>not</strong> auto-generated by the database. You must manage the value yourself — via triggers, <code>SaveChanges</code> interceptors, or manual assignment. If you forget to increment the version, the concurrency check never fires and you silently lose the protection.</p>
<h3 id="application-code-vs-database-trigger">Application Code vs Database Trigger</h3>
<table>
  <thead>
      <tr>
          <th>Aspect</th>
          <th>Application Code (C#)</th>
          <th>Database Trigger</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Location</td>
          <td>Application layer (interceptor)</td>
          <td>Database (SQL migration)</td>
      </tr>
      <tr>
          <td>EF Core Config</td>
          <td><code>.IsConcurrencyToken()</code> only</td>
          <td><code>.IsConcurrencyToken().ValueGeneratedOnAddOrUpdate()</code></td>
      </tr>
      <tr>
          <td>Portability</td>
          <td>Fully portable across databases</td>
          <td>Database-specific SQL</td>
      </tr>
      <tr>
          <td>Value Generation</td>
          <td>App increments via interceptor</td>
          <td>Trigger increments automatically</td>
      </tr>
      <tr>
          <td>Transparency</td>
          <td>Visible in application code</td>
          <td>Transparent to EF Core</td>
      </tr>
      <tr>
          <td>Testing</td>
          <td>Easy to unit test</td>
          <td>Requires database</td>
      </tr>
  </tbody>
</table>
<h3 id="database-comparison">Database Comparison</h3>
<table>
  <thead>
      <tr>
          <th>Database</th>
          <th>Native Concurrency Token</th>
          <th>Integer Version Support</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>SQL Server</td>
          <td><code>ROWVERSION</code> (auto-incrementing 8-byte binary)</td>
          <td>Trigger or application code (interceptor)</td>
      </tr>
      <tr>
          <td>PostgreSQL</td>
          <td><code>xmin</code> (transaction ID, wraps on wraparound)</td>
          <td>Trigger or application-managed</td>
      </tr>
      <tr>
          <td>SQLite</td>
          <td>None</td>
          <td>Application-managed only (trigger or code)</td>
      </tr>
      <tr>
          <td>MySQL</td>
          <td>None (InnoDB uses MVCC internally)</td>
          <td>Trigger or application-managed</td>
      </tr>
  </tbody>
</table>
<h2 id="pattern-5--outbox-for-consistent-integration-events">Pattern 5 — Outbox for consistent integration events</h2>
<p>When publishing domain events, persist them in the same transaction as your aggregate changes, then deliver asynchronously from an <a href="https://learn.microsoft.com/en-us/dotnet/architecture/microservices/microservice-ddd-cqrs-patterns/implementation-domain-events#create-the-outbox-table">outbox table</a>. This avoids double-publish or lost-publish scenarios during retries.</p>
<ul>
<li>Transaction 1: Save aggregate + append events to Outbox.</li>
<li>Background worker: Reads Outbox, publishes, marks as processed.</li>
</ul>
<p>Libraries: MassTransit Outbox, NServiceBus Outbox, or a simple table with a background service.</p>
<h2 id="operational-guidance">Operational guidance</h2>
<table>
  <thead>
      <tr>
          <th>Guideline</th>
          <th>Why It Matters</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Keep aggregates small and cohesive</td>
          <td>Fewer fields means fewer conflicts. If <code>UserPreferences</code> and <code>UserProfile</code> change independently, split them into separate aggregates. A large aggregate is a contention magnet.</td>
      </tr>
      <tr>
          <td>Prefer command-side retries</td>
          <td>Server-side retries are faster (no HTTP round-trip) and don&rsquo;t waste client resources. Use background workers for non-interactive retry scenarios.</td>
      </tr>
      <tr>
          <td>Log and surface correlation IDs</td>
          <td>Critical for debugging concurrency issues in production. Include them in error responses so clients and support engineers can trace the conflict: <code>{&quot;error&quot;: &quot;Conflict&quot;, &quot;correlationId&quot;: &quot;abc-123&quot;}</code>.</td>
      </tr>
      <tr>
          <td>Load only what you need</td>
          <td>Don&rsquo;t load the entire object graph for a simple status update. Include navigation properties only when invariants require them. Smaller loaded graphs reduce memory usage and conflict surface area.</td>
      </tr>
  </tbody>
</table>
<p><strong>PostgreSQL Note:</strong> Using <code>xmin</code> as the concurrency token avoids adding an extra column, but be aware that <code>xmin</code> values can be frozen and reused in long-running transactions due to VACUUM. For high-concurrency production systems, an explicit integer <code>Version</code> column with a trigger is safer and more predictable.</p>
<!-- raw HTML omitted -->
<!-- raw HTML omitted -->
<h2 id="faq">FAQ</h2>
<h3 id="is-pessimistic-locking-ever-needed-in-ddd">Is pessimistic locking ever needed in DDD?</h3>
<p>Rarely. Use it for short, high-contention critical sections where optimistic retries are too expensive. Database-specific locks can hurt scalability.</p>
<table>
  <thead>
      <tr>
          <th>Aspect</th>
          <th>Optimistic Locking</th>
          <th>Pessimistic Locking</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Conflict detection</td>
          <td>At write time</td>
          <td>Prevented by locks</td>
      </tr>
      <tr>
          <td>Blocking</td>
          <td>None — readers and writers proceed freely</td>
          <td>Readers block writers; writers block readers</td>
      </tr>
      <tr>
          <td>Throughput</td>
          <td>High under low contention</td>
          <td>Lower due to lock acquisition overhead</td>
      </tr>
      <tr>
          <td>Deadlock risk</td>
          <td>None</td>
          <td>Significant — locks must be acquired in a consistent order</td>
      </tr>
      <tr>
          <td>Retry cost</td>
          <td>Full re-read and re-write</td>
          <td>None — the transaction waits and then proceeds</td>
      </tr>
      <tr>
          <td>Best for</td>
          <td>Read-heavy workloads, web applications, low contention</td>
          <td>High contention, short critical sections, financial correctness</td>
      </tr>
      <tr>
          <td>Web applications</td>
          <td>Natural fit (stateless, short transactions)</td>
          <td>Difficult — long user-think time means long lock holds</td>
      </tr>
  </tbody>
</table>
<p><strong>When pessimistic locking IS needed:</strong></p>
<ul>
<li>Financial transactions where correctness is paramount and the cost of a wrong answer is high</li>
<li>Flash sales with hundreds of concurrent requests hitting the same inventory row</li>
<li>Background batch jobs where waiting is acceptable and retrying is expensive</li>
<li>Operations that must complete atomically across multiple tables without any possibility of conflict</li>
</ul>
<p><strong>When to avoid pessimistic locking:</strong></p>
<ul>
<li>Web applications with long user-think time (users editing forms for minutes)</li>
<li>Systems requiring high throughput (locks serialize access and reduce parallelism)</li>
<li>Scenarios with complex aggregate boundaries (locks don&rsquo;t span aggregate roots cleanly)</li>
</ul>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sql" data-lang="sql"><span class="line"><span class="cl"><span class="c1">-- SQL Server: pessimistic lock via table hint
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="k">SELECT</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="k">FROM</span><span class="w"> </span><span class="p">[</span><span class="n">Orders</span><span class="p">]</span><span class="w"> </span><span class="k">WITH</span><span class="w"> </span><span class="p">(</span><span class="n">UPDLOCK</span><span class="p">,</span><span class="w"> </span><span class="n">ROWLOCK</span><span class="p">)</span><span class="w"> </span><span class="k">WHERE</span><span class="w"> </span><span class="p">[</span><span class="n">Id</span><span class="p">]</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="o">@</span><span class="n">id</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="c1">-- PostgreSQL: pessimistic lock via SELECT ... FOR UPDATE
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="k">SELECT</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="k">FROM</span><span class="w"> </span><span class="n">orders</span><span class="w"> </span><span class="k">WHERE</span><span class="w"> </span><span class="n">id</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="o">@</span><span class="n">id</span><span class="w"> </span><span class="k">FOR</span><span class="w"> </span><span class="k">UPDATE</span><span class="p">;</span><span class="w">
</span></span></span></code></pre></div><h3 id="should-the-concurrency-token-live-in-the-domain-model">Should the concurrency token live in the domain model?</h3>
<p>It can, but many teams treat it as a persistence concern (still exposed as ETag). Keep the domain focused on invariants; keep infrastructure details in infrastructure.</p>
<p>There are three common approaches:</p>
<table>
  <thead>
      <tr>
          <th>Approach</th>
          <th>Token Location</th>
          <th>Pros</th>
          <th>Cons</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><strong>In Domain Model</strong></td>
          <td><code>public byte[] RowVersion { get; private set; }</code> on the aggregate root</td>
          <td>Domain knows about concurrency; can make version-dependent decisions</td>
          <td>Contaminates the domain with persistence concerns</td>
      </tr>
      <tr>
          <td><strong>In Infrastructure</strong></td>
          <td>Shadow property or EF Core configuration only</td>
          <td>Domain stays pure and persistence-ignorant</td>
          <td>Domain cannot reason about version conflicts</td>
      </tr>
      <tr>
          <td><strong>Exposed as ETag only</strong></td>
          <td>API layer maps <code>RowVersion</code> to an <code>ETag</code> response header</td>
          <td>Clean separation of concerns</td>
          <td>Requires a mapping layer between API and domain</td>
      </tr>
  </tbody>
</table>
<p><strong>Recommended approach for most teams:</strong> Keep the concurrency token on the aggregate root (e.g., <code>RowVersion</code>), but configure its persistence behavior in the infrastructure layer. This way the domain model is aware that concurrency exists — which is a real business concern — but does not know the mechanical details of how it is enforced.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="c1">// Domain layer — aware of concurrency as a business concept</span>
</span></span><span class="line"><span class="cl"><span class="kd">public</span> <span class="k">class</span> <span class="nc">Order</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="n">Guid</span> <span class="n">Id</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="kd">private</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="kt">byte</span><span class="p">[]</span> <span class="n">RowVersion</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="kd">private</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span> <span class="p">=</span> <span class="n">Array</span><span class="p">.</span><span class="n">Empty</span><span class="p">&lt;</span><span class="kt">byte</span><span class="p">&gt;();</span>
</span></span><span class="line"><span class="cl">    <span class="kd">private</span> <span class="k">readonly</span> <span class="n">List</span><span class="p">&lt;</span><span class="n">OrderLine</span><span class="p">&gt;</span> <span class="n">_lines</span> <span class="p">=</span> <span class="k">new</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="k">void</span> <span class="n">Submit</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="p">(!</span><span class="n">_lines</span><span class="p">.</span><span class="n">Any</span><span class="p">())</span> <span class="k">throw</span> <span class="k">new</span> <span class="n">InvalidOperationException</span><span class="p">(</span><span class="s">&#34;Cannot submit an empty order.&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="n">Status</span> <span class="p">=</span> <span class="s">&#34;Submitted&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">// Infrastructure layer — configures how the token is persisted</span>
</span></span><span class="line"><span class="cl"><span class="n">modelBuilder</span><span class="p">.</span><span class="n">Entity</span><span class="p">&lt;</span><span class="n">Order</span><span class="p">&gt;()</span>
</span></span><span class="line"><span class="cl">    <span class="p">.</span><span class="n">Property</span><span class="p">(</span><span class="n">o</span> <span class="p">=&gt;</span> <span class="n">o</span><span class="p">.</span><span class="n">RowVersion</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">.</span><span class="n">IsRowVersion</span><span class="p">();</span>
</span></span></code></pre></div><hr>
<h2 id="real-world-implementation-e-commerce-order-service">Real-World Implementation: E-Commerce Order Service</h2>
<p>Here is a complete, step-by-step implementation of an order service that applies all the patterns discussed above.</p>
<h3 id="step-1-define-your-aggregate-root-with-a-concurrency-token">Step 1: Define Your Aggregate Root with a Concurrency Token</h3>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="kd">public</span> <span class="k">class</span> <span class="nc">Order</span> <span class="c1">// Aggregate Root</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="n">Guid</span> <span class="n">Id</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="kd">private</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="n">Guid</span> <span class="n">CustomerId</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="kd">private</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="kt">string</span> <span class="n">Status</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="kd">private</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span> <span class="p">=</span> <span class="s">&#34;Draft&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kd">private</span> <span class="k">readonly</span> <span class="n">List</span><span class="p">&lt;</span><span class="n">OrderLine</span><span class="p">&gt;</span> <span class="n">_lines</span> <span class="p">=</span> <span class="k">new</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="n">IReadOnlyCollection</span><span class="p">&lt;</span><span class="n">OrderLine</span><span class="p">&gt;</span> <span class="n">Lines</span> <span class="p">=&gt;</span> <span class="n">_lines</span><span class="p">.</span><span class="n">AsReadOnly</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// Concurrency token — EF Core manages the value on INSERT and UPDATE</span>
</span></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="kt">byte</span><span class="p">[]</span> <span class="n">RowVersion</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="kd">private</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span> <span class="p">=</span> <span class="n">Array</span><span class="p">.</span><span class="n">Empty</span><span class="p">&lt;</span><span class="kt">byte</span><span class="p">&gt;();</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="k">void</span> <span class="n">AddLine</span><span class="p">(</span><span class="n">Guid</span> <span class="n">productId</span><span class="p">,</span> <span class="kt">int</span> <span class="n">quantity</span><span class="p">,</span> <span class="kt">decimal</span> <span class="n">unitPrice</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="p">(</span><span class="n">quantity</span> <span class="p">&lt;=</span> <span class="m">0</span><span class="p">)</span> <span class="k">throw</span> <span class="k">new</span> <span class="n">ArgumentOutOfRangeException</span><span class="p">(</span><span class="n">nameof</span><span class="p">(</span><span class="n">quantity</span><span class="p">));</span>
</span></span><span class="line"><span class="cl">        <span class="n">_lines</span><span class="p">.</span><span class="n">Add</span><span class="p">(</span><span class="k">new</span> <span class="n">OrderLine</span><span class="p">(</span><span class="n">productId</span><span class="p">,</span> <span class="n">quantity</span><span class="p">,</span> <span class="n">unitPrice</span><span class="p">));</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="k">void</span> <span class="n">Submit</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="p">(!</span><span class="n">_lines</span><span class="p">.</span><span class="n">Any</span><span class="p">())</span> <span class="k">throw</span> <span class="k">new</span> <span class="n">InvalidOperationException</span><span class="p">(</span><span class="s">&#34;Cannot submit an empty order.&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="n">Status</span> <span class="p">=</span> <span class="s">&#34;Submitted&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kd">public</span> <span class="k">record</span> <span class="nc">OrderLine</span><span class="p">(</span><span class="n">Guid</span> <span class="n">ProductId</span><span class="p">,</span> <span class="kt">int</span> <span class="n">Quantity</span><span class="p">,</span> <span class="kt">decimal</span> <span class="n">UnitPrice</span><span class="p">);</span>
</span></span></code></pre></div><h3 id="step-2-configure-ef-core">Step 2: Configure EF Core</h3>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="kd">public</span> <span class="kd">sealed</span> <span class="k">class</span> <span class="nc">OrderingDbContext</span> <span class="p">:</span> <span class="n">DbContext</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="n">DbSet</span><span class="p">&lt;</span><span class="n">Order</span><span class="p">&gt;</span> <span class="n">Orders</span> <span class="p">=&gt;</span> <span class="n">Set</span><span class="p">&lt;</span><span class="n">Order</span><span class="p">&gt;();</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="kd">protected</span> <span class="kd">override</span> <span class="k">void</span> <span class="n">OnModelCreating</span><span class="p">(</span><span class="n">ModelBuilder</span> <span class="n">modelBuilder</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="kt">var</span> <span class="n">order</span> <span class="p">=</span> <span class="n">modelBuilder</span><span class="p">.</span><span class="n">Entity</span><span class="p">&lt;</span><span class="n">Order</span><span class="p">&gt;();</span>
</span></span><span class="line"><span class="cl">        <span class="n">order</span><span class="p">.</span><span class="n">HasKey</span><span class="p">(</span><span class="n">o</span> <span class="p">=&gt;</span> <span class="n">o</span><span class="p">.</span><span class="n">Id</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="n">order</span><span class="p">.</span><span class="n">Property</span><span class="p">(</span><span class="n">o</span> <span class="p">=&gt;</span> <span class="n">o</span><span class="p">.</span><span class="n">RowVersion</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">             <span class="p">.</span><span class="n">IsRowVersion</span><span class="p">()</span>  <span class="c1">// marks as concurrency token</span>
</span></span><span class="line"><span class="cl">             <span class="p">.</span><span class="n">IsRequired</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">        <span class="n">order</span><span class="p">.</span><span class="n">OwnsMany</span><span class="p">(</span><span class="n">o</span> <span class="p">=&gt;</span> <span class="n">o</span><span class="p">.</span><span class="n">Lines</span><span class="p">,</span> <span class="n">b</span> <span class="p">=&gt;</span>
</span></span><span class="line"><span class="cl">        <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="n">b</span><span class="p">.</span><span class="n">WithOwner</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">            <span class="n">b</span><span class="p">.</span><span class="n">Property</span><span class="p">&lt;</span><span class="kt">int</span><span class="p">&gt;(</span><span class="s">&#34;Id&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">            <span class="n">b</span><span class="p">.</span><span class="n">HasKey</span><span class="p">(</span><span class="s">&#34;Id&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="p">});</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><h3 id="step-3-handle-conflicts-in-the-repository">Step 3: Handle Conflicts in the Repository</h3>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="kd">public</span> <span class="k">class</span> <span class="nc">OrdersRepository</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kd">private</span> <span class="k">readonly</span> <span class="n">OrderingDbContext</span> <span class="n">_db</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="n">OrdersRepository</span><span class="p">(</span><span class="n">OrderingDbContext</span> <span class="n">db</span><span class="p">)</span> <span class="p">=&gt;</span> <span class="n">_db</span> <span class="p">=</span> <span class="n">db</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="kd">async</span> <span class="n">Task</span> <span class="n">SaveAsync</span><span class="p">(</span><span class="n">Order</span> <span class="n">order</span><span class="p">,</span> <span class="n">CancellationToken</span> <span class="n">ct</span> <span class="p">=</span> <span class="k">default</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="k">try</span>
</span></span><span class="line"><span class="cl">        <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="k">await</span> <span class="n">_db</span><span class="p">.</span><span class="n">SaveChangesAsync</span><span class="p">(</span><span class="n">ct</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">        <span class="k">catch</span> <span class="p">(</span><span class="n">DbUpdateConcurrencyException</span> <span class="n">ex</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="k">throw</span> <span class="k">new</span> <span class="n">ConcurrencyException</span><span class="p">(</span><span class="s">&#34;Order was modified by another process.&#34;</span><span class="p">,</span> <span class="n">ex</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kd">public</span> <span class="kd">sealed</span> <span class="k">class</span> <span class="nc">ConcurrencyException</span> <span class="p">:</span> <span class="n">Exception</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="n">ConcurrencyException</span><span class="p">(</span><span class="kt">string</span> <span class="n">message</span><span class="p">,</span> <span class="n">Exception</span><span class="p">?</span> <span class="n">inner</span> <span class="p">=</span> <span class="kc">null</span><span class="p">)</span> <span class="p">:</span> <span class="k">base</span><span class="p">(</span><span class="n">message</span><span class="p">,</span> <span class="n">inner</span><span class="p">)</span> <span class="p">{}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><h3 id="step-4-expose-etags-and-require-if-match-in-the-api">Step 4: Expose ETags and Require If-Match in the API</h3>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="na">[ApiController]</span>
</span></span><span class="line"><span class="cl"><span class="na">[Route(&#34;api/orders&#34;)]</span>
</span></span><span class="line"><span class="cl"><span class="kd">public</span> <span class="k">class</span> <span class="nc">OrdersController</span> <span class="p">:</span> <span class="n">ControllerBase</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kd">private</span> <span class="k">readonly</span> <span class="n">OrderingDbContext</span> <span class="n">_db</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kd">private</span> <span class="k">readonly</span> <span class="n">OrdersRepository</span> <span class="n">_repo</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="n">OrdersController</span><span class="p">(</span><span class="n">OrderingDbContext</span> <span class="n">db</span><span class="p">,</span> <span class="n">OrdersRepository</span> <span class="n">repo</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">_db</span> <span class="p">=</span> <span class="n">db</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="n">_repo</span> <span class="p">=</span> <span class="n">repo</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="na">
</span></span></span><span class="line"><span class="cl"><span class="na">    [HttpGet(&#34;{id}&#34;)]</span>
</span></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="kd">async</span> <span class="n">Task</span><span class="p">&lt;</span><span class="n">IActionResult</span><span class="p">&gt;</span> <span class="n">Get</span><span class="p">(</span><span class="n">Guid</span> <span class="n">id</span><span class="p">,</span> <span class="n">CancellationToken</span> <span class="n">ct</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="kt">var</span> <span class="n">order</span> <span class="p">=</span> <span class="k">await</span> <span class="n">_db</span><span class="p">.</span><span class="n">Orders</span><span class="p">.</span><span class="n">Include</span><span class="p">(</span><span class="n">o</span> <span class="p">=&gt;</span> <span class="n">o</span><span class="p">.</span><span class="n">Lines</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">            <span class="p">.</span><span class="n">FirstOrDefaultAsync</span><span class="p">(</span><span class="n">o</span> <span class="p">=&gt;</span> <span class="n">o</span><span class="p">.</span><span class="n">Id</span> <span class="p">==</span> <span class="n">id</span><span class="p">,</span> <span class="n">ct</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="p">(</span><span class="n">order</span> <span class="k">is</span> <span class="kc">null</span><span class="p">)</span> <span class="k">return</span> <span class="n">NotFound</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="c1">// Expose the concurrency token as an ETag (quoted per RFC 7232)</span>
</span></span><span class="line"><span class="cl">        <span class="n">Response</span><span class="p">.</span><span class="n">Headers</span><span class="p">.</span><span class="n">ETag</span> <span class="p">=</span> <span class="s">$&#34;\&#34;</span><span class="p">{</span><span class="n">Convert</span><span class="p">.</span><span class="n">ToBase64String</span><span class="p">(</span><span class="n">order</span><span class="p">.</span><span class="n">RowVersion</span><span class="p">)}</span><span class="err">\</span><span class="s">&#34;&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="n">Ok</span><span class="p">(</span><span class="n">order</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="na">
</span></span></span><span class="line"><span class="cl"><span class="na">    [HttpPut(&#34;{id}&#34;)]</span>
</span></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="kd">async</span> <span class="n">Task</span><span class="p">&lt;</span><span class="n">IActionResult</span><span class="p">&gt;</span> <span class="n">Update</span><span class="p">(</span><span class="n">Guid</span> <span class="n">id</span><span class="p">,</span> <span class="n">UpdateOrderDto</span> <span class="n">dto</span><span class="p">,</span> <span class="n">CancellationToken</span> <span class="n">ct</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="c1">// Enforce If-Match — return 428 if missing</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="p">(!</span><span class="n">Request</span><span class="p">.</span><span class="n">Headers</span><span class="p">.</span><span class="n">TryGetValue</span><span class="p">(</span><span class="s">&#34;If-Match&#34;</span><span class="p">,</span> <span class="k">out</span> <span class="kt">var</span> <span class="n">etagHeader</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">            <span class="k">return</span> <span class="n">StatusCode</span><span class="p">(</span><span class="n">StatusCodes</span><span class="p">.</span><span class="n">Status428PreconditionRequired</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                <span class="s">&#34;Include If-Match header with the current ETag.&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="kt">var</span> <span class="n">expected</span> <span class="p">=</span> <span class="n">ParseEtag</span><span class="p">(</span><span class="n">etagHeader</span><span class="p">!);</span>
</span></span><span class="line"><span class="cl">        <span class="kt">var</span> <span class="n">order</span> <span class="p">=</span> <span class="k">await</span> <span class="n">_db</span><span class="p">.</span><span class="n">Orders</span><span class="p">.</span><span class="n">Include</span><span class="p">(</span><span class="n">o</span> <span class="p">=&gt;</span> <span class="n">o</span><span class="p">.</span><span class="n">Lines</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">            <span class="p">.</span><span class="n">FirstOrDefaultAsync</span><span class="p">(</span><span class="n">o</span> <span class="p">=&gt;</span> <span class="n">o</span><span class="p">.</span><span class="n">Id</span> <span class="p">==</span> <span class="n">id</span><span class="p">,</span> <span class="n">ct</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="p">(</span><span class="n">order</span> <span class="k">is</span> <span class="kc">null</span><span class="p">)</span> <span class="k">return</span> <span class="n">NotFound</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="c1">// Return 412 if the ETag is stale</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="p">(!</span><span class="n">order</span><span class="p">.</span><span class="n">RowVersion</span><span class="p">.</span><span class="n">AsSpan</span><span class="p">().</span><span class="n">SequenceEqual</span><span class="p">(</span><span class="n">expected</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">            <span class="k">return</span> <span class="n">StatusCode</span><span class="p">(</span><span class="n">StatusCodes</span><span class="p">.</span><span class="n">Status412PreconditionFailed</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="n">order</span><span class="p">.</span><span class="n">Submit</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="k">try</span>
</span></span><span class="line"><span class="cl">        <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="k">await</span> <span class="n">_repo</span><span class="p">.</span><span class="n">SaveAsync</span><span class="p">(</span><span class="n">order</span><span class="p">,</span> <span class="n">ct</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">            <span class="k">return</span> <span class="n">NoContent</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">        <span class="k">catch</span> <span class="p">(</span><span class="n">ConcurrencyException</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="k">return</span> <span class="n">Conflict</span><span class="p">();</span> <span class="c1">// 409</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="kd">static</span> <span class="kt">byte</span><span class="p">[]</span> <span class="n">ParseEtag</span><span class="p">(</span><span class="kt">string</span> <span class="n">etag</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="kt">var</span> <span class="n">trimmed</span> <span class="p">=</span> <span class="n">etag</span><span class="p">.</span><span class="n">Trim</span><span class="p">(</span><span class="sc">&#39;&#34;&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">            <span class="k">return</span> <span class="n">Convert</span><span class="p">.</span><span class="n">FromBase64String</span><span class="p">(</span><span class="n">trimmed</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><h3 id="step-5-add-a-retry-policy-for-automated-operations">Step 5: Add a Retry Policy for Automated Operations</h3>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="k">using</span> <span class="nn">Polly</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="k">using</span> <span class="nn">Polly.Retry</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kd">public</span> <span class="kd">sealed</span> <span class="k">class</span> <span class="nc">SubmitOrderHandler</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kd">private</span> <span class="k">readonly</span> <span class="n">OrdersRepository</span> <span class="n">_repo</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kd">private</span> <span class="k">readonly</span> <span class="n">OrderingDbContext</span> <span class="n">_db</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="kd">private</span> <span class="kd">static</span> <span class="k">readonly</span> <span class="n">ResiliencePipeline</span> <span class="n">Retry</span> <span class="p">=</span> <span class="k">new</span> <span class="n">ResiliencePipelineBuilder</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">        <span class="p">.</span><span class="n">AddRetry</span><span class="p">(</span><span class="k">new</span> <span class="n">RetryStrategyOptions</span>
</span></span><span class="line"><span class="cl">        <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="n">MaxRetryAttempts</span> <span class="p">=</span> <span class="m">3</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="n">Delay</span> <span class="p">=</span> <span class="n">TimeSpan</span><span class="p">.</span><span class="n">FromMilliseconds</span><span class="p">(</span><span class="m">50</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">            <span class="n">BackoffType</span> <span class="p">=</span> <span class="n">DelayBackoffType</span><span class="p">.</span><span class="n">Exponential</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="n">ShouldHandle</span> <span class="p">=</span> <span class="k">new</span> <span class="n">PredicateBuilder</span><span class="p">().</span><span class="n">Handle</span><span class="p">&lt;</span><span class="n">ConcurrencyException</span><span class="p">&gt;(),</span>
</span></span><span class="line"><span class="cl">            <span class="n">OnRetry</span> <span class="p">=</span> <span class="n">args</span> <span class="p">=&gt;</span>
</span></span><span class="line"><span class="cl">            <span class="p">{</span>
</span></span><span class="line"><span class="cl">                <span class="n">Console</span><span class="p">.</span><span class="n">WriteLine</span><span class="p">(</span><span class="s">$&#34;Retry {args.AttemptNumber} after {args.RetryDelay.TotalMilliseconds}ms&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">                <span class="k">return</span> <span class="n">ValueTask</span><span class="p">.</span><span class="n">CompletedTask</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">            <span class="p">},</span>
</span></span><span class="line"><span class="cl">        <span class="p">})</span>
</span></span><span class="line"><span class="cl">        <span class="p">.</span><span class="n">Build</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="n">SubmitOrderHandler</span><span class="p">(</span><span class="n">OrdersRepository</span> <span class="n">repo</span><span class="p">,</span> <span class="n">OrderingDbContext</span> <span class="n">db</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">_repo</span> <span class="p">=</span> <span class="n">repo</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="n">_db</span> <span class="p">=</span> <span class="n">db</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="n">Task</span> <span class="n">HandleAsync</span><span class="p">(</span><span class="n">Guid</span> <span class="n">orderId</span><span class="p">,</span> <span class="n">CancellationToken</span> <span class="n">ct</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="p">=&gt;</span> <span class="n">Retry</span><span class="p">.</span><span class="n">ExecuteAsync</span><span class="p">(</span><span class="kd">async</span> <span class="n">_</span> <span class="p">=&gt;</span>
</span></span><span class="line"><span class="cl">        <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="c1">// IMPORTANT: Detach stale entity before re-reading</span>
</span></span><span class="line"><span class="cl">            <span class="kt">var</span> <span class="n">existing</span> <span class="p">=</span> <span class="n">_db</span><span class="p">.</span><span class="n">Orders</span><span class="p">.</span><span class="n">Local</span><span class="p">.</span><span class="n">FirstOrDefault</span><span class="p">(</span><span class="n">o</span> <span class="p">=&gt;</span> <span class="n">o</span><span class="p">.</span><span class="n">Id</span> <span class="p">==</span> <span class="n">orderId</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">            <span class="k">if</span> <span class="p">(</span><span class="n">existing</span> <span class="p">!=</span> <span class="kc">null</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">                <span class="n">_db</span><span class="p">.</span><span class="n">Entry</span><span class="p">(</span><span class="n">existing</span><span class="p">).</span><span class="n">State</span> <span class="p">=</span> <span class="n">EntityState</span><span class="p">.</span><span class="n">Detached</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">            <span class="kt">var</span> <span class="n">order</span> <span class="p">=</span> <span class="k">await</span> <span class="n">_db</span><span class="p">.</span><span class="n">Orders</span><span class="p">.</span><span class="n">Include</span><span class="p">(</span><span class="n">o</span> <span class="p">=&gt;</span> <span class="n">o</span><span class="p">.</span><span class="n">Lines</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">                <span class="p">.</span><span class="n">FirstAsync</span><span class="p">(</span><span class="n">o</span> <span class="p">=&gt;</span> <span class="n">o</span><span class="p">.</span><span class="n">Id</span> <span class="p">==</span> <span class="n">orderId</span><span class="p">,</span> <span class="n">ct</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">            <span class="n">order</span><span class="p">.</span><span class="n">Submit</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">            <span class="k">await</span> <span class="n">_repo</span><span class="p">.</span><span class="n">SaveAsync</span><span class="p">(</span><span class="n">order</span><span class="p">,</span> <span class="n">ct</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="p">},</span> <span class="n">ct</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><h3 id="implementation-checklist">Implementation Checklist</h3>
<ul>
<li>Add <code>RowVersion</code> or <code>Version</code> to every aggregate root</li>
<li>Configure it as a concurrency token (<code>.IsRowVersion()</code> or <code>.IsConcurrencyToken()</code>)</li>
<li>Handle <code>DbUpdateConcurrencyException</code> in the repository layer</li>
<li>Expose the token as an <code>ETag</code> in <code>GET</code> responses</li>
<li>Require <code>If-Match</code> for <code>PUT</code>/<code>PATCH</code> operations</li>
<li>Return <code>428</code> if <code>If-Match</code> is missing</li>
<li>Return <code>412</code> if the <code>ETag</code> does not match</li>
<li>Return <code>409</code> for business-logic conflicts</li>
<li>Add a retry policy with exponential backoff and jitter for automated operations</li>
<li>Include correlation IDs in error responses</li>
<li>Test concurrent scenarios with load-testing tools (k6, Apache Bench, etc.)</li>
</ul>
<hr>
<h2 id="references">References</h2>
<ul>
<li><a href="https://learn.microsoft.com/en-us/ef/core/saving/concurrency">Handling Concurrency Conflicts — EF Core</a></li>
<li><a href="https://learn.microsoft.com/en-us/sql/t-sql/data-types/rowversion-transact-sql">SQL Server RowVersion Data Type</a></li>
<li><a href="https://www.postgresql.org/docs/current/ddl-system-columns.html">PostgreSQL System Columns (xmin)</a></li>
<li><a href="https://www.npgsql.org/efcore/modeling/concurrency.html">Npgsql EF Core Concurrency Tokens</a></li>
<li><a href="https://www.bricelam.net/2020/08/07/sqlite-and-efcore-concurrency-tokens.html">SQLite and EF Core Concurrency Tokens</a></li>
<li><a href="https://www.pollydocs.org/strategies/retry.html">Polly Retry Resilience Strategy</a></li>
<li><a href="https://event-driven.io/en/how_to_use_etag_header_for_optimistic_concurrency/">How to Use ETag Header for Optimistic Concurrency</a></li>
</ul>
]]></content:encoded>
    </item>
  </channel>
</rss>
