<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Pablo's Tech Insights | .Net | Angular]]></title><description><![CDATA[Seasoned Software Engineer | Microsoft Technology Specialist | Over a Decade of Expertise in Web Applications | Proficient in Angular & React | Dedicated to .NE]]></description><link>https://pablorivas.eu</link><generator>RSS for Node</generator><lastBuildDate>Thu, 15 Oct 2026 02:13:26 GMT</lastBuildDate><atom:link href="https://pablorivas.eu/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[PostgreSQL Row Level Security with .NET and EF Core]]></title><description><![CDATA[Row Level Security (RLS) is PostgreSQL's built-in feature for controlling which rows users can access in database tables. Instead of granting access to entire tables, RLS applies automatic filters at the row level, creating invisible WHERE clauses th...]]></description><link>https://pablorivas.eu/postgresql-row-level-security-with-net-and-ef-core</link><guid isPermaLink="true">https://pablorivas.eu/postgresql-row-level-security-with-net-and-ef-core</guid><category><![CDATA[PostgreSQL]]></category><category><![CDATA[efcore]]></category><category><![CDATA[asp.net core]]></category><category><![CDATA[.net core]]></category><dc:creator><![CDATA[Pablo Rivas Sánchez]]></dc:creator><pubDate>Sat, 24 Jan 2026 16:07:48 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1769270778135/d5eba348-e97a-4db5-b794-2d94c74722a0.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Row Level Security (RLS) is PostgreSQL's built-in feature for controlling which rows users can access in database tables. Instead of granting access to entire tables, RLS applies automatic filters at the row level, creating invisible WHERE clauses that protect your data without touching application code.</p>
<h2 id="heading-why-rls-matters">Why RLS Matters</h2>
<p><strong>Defense Against Code Bugs</strong>: Application-level filters are fragile. One forgotten <code>WHERE tenant_id = @id</code> and you've leaked data across tenants. RLS enforces protection at the database level, catching what your code misses.</p>
<p><strong>Cleaner Code</strong>: Without RLS, every query needs explicit tenant filtering scattered across your codebase. RLS centralizes this logic in the database, eliminating repetitive security code.</p>
<p><strong>Multi-Tenant Security</strong>: For SaaS applications sharing databases, RLS delivers single-schema simplicity with isolation guarantees approaching physical separation. One connection pool, one database, strict tenant boundaries.</p>
<h2 id="heading-implementation-guide">Implementation Guide</h2>
<h3 id="heading-1-configure-postgresql">1. Configure PostgreSQL</h3>
<p>Enable RLS and create policies for your table:</p>
<pre><code class="lang-sql"><span class="hljs-keyword">CREATE</span> <span class="hljs-keyword">TABLE</span> projects (
    <span class="hljs-keyword">id</span> <span class="hljs-built_in">SERIAL</span> PRIMARY <span class="hljs-keyword">KEY</span>,
    tenant_id <span class="hljs-keyword">UUID</span> <span class="hljs-keyword">NOT</span> <span class="hljs-literal">NULL</span>,
    <span class="hljs-keyword">name</span> <span class="hljs-built_in">TEXT</span> <span class="hljs-keyword">NOT</span> <span class="hljs-literal">NULL</span>,
    description <span class="hljs-built_in">TEXT</span>
);

<span class="hljs-keyword">ALTER</span> <span class="hljs-keyword">TABLE</span> projects <span class="hljs-keyword">ENABLE</span> <span class="hljs-keyword">ROW</span> <span class="hljs-keyword">LEVEL</span> <span class="hljs-keyword">SECURITY</span>;

<span class="hljs-comment">-- Filter reads, writes, updates, and deletes by tenant</span>
<span class="hljs-keyword">CREATE</span> <span class="hljs-keyword">POLICY</span> tenant_isolation <span class="hljs-keyword">ON</span> projects
    <span class="hljs-keyword">FOR</span> <span class="hljs-keyword">ALL</span>
    <span class="hljs-keyword">USING</span> (tenant_id = current_setting(<span class="hljs-string">'app.current_tenant'</span>)::<span class="hljs-keyword">UUID</span>)
    <span class="hljs-keyword">WITH</span> <span class="hljs-keyword">CHECK</span> (tenant_id = current_setting(<span class="hljs-string">'app.current_tenant'</span>)::<span class="hljs-keyword">UUID</span>);
</code></pre>
<h3 id="heading-2-extract-tenant-context">2. Extract Tenant Context</h3>
<p>Pull the tenant ID from authenticated user claims:</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">public</span> <span class="hljs-keyword">interface</span> <span class="hljs-title">ITenantProvider</span>
{
    <span class="hljs-keyword">string</span>? GetCurrentTenantId();
}

<span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">JwtTenantProvider</span> : <span class="hljs-title">ITenantProvider</span>
{
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">readonly</span> IHttpContextAccessor _httpContextAccessor;

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">JwtTenantProvider</span>(<span class="hljs-params">IHttpContextAccessor httpContextAccessor</span>)</span>
    {
        _httpContextAccessor = httpContextAccessor;
    }

    <span class="hljs-keyword">public</span> <span class="hljs-keyword">string</span>? GetCurrentTenantId()
    {
        <span class="hljs-keyword">return</span> _httpContextAccessor.HttpContext?.User
            .FindFirst(<span class="hljs-string">"tenant_id"</span>)?.Value;
    }
}
</code></pre>
<h3 id="heading-3-create-ef-core-interceptor">3. Create EF Core Interceptor</h3>
<p>Set the session variable when connections open:</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">RlsConnectionInterceptor</span> : <span class="hljs-title">DbConnectionInterceptor</span>
{
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">readonly</span> ITenantProvider _tenantProvider;

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">RlsConnectionInterceptor</span>(<span class="hljs-params">ITenantProvider tenantProvider</span>)</span>
    {
        _tenantProvider = tenantProvider;
    }

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">override</span> <span class="hljs-keyword">async</span> Task <span class="hljs-title">ConnectionOpenedAsync</span>(<span class="hljs-params">
        DbConnection connection,
        ConnectionEndEventData eventData,
        CancellationToken cancellationToken = <span class="hljs-keyword">default</span></span>)</span>
    {
        <span class="hljs-keyword">var</span> tenantId = _tenantProvider.GetCurrentTenantId();

        <span class="hljs-keyword">if</span> (!<span class="hljs-keyword">string</span>.IsNullOrEmpty(tenantId))
        {
            <span class="hljs-keyword">using</span> <span class="hljs-keyword">var</span> command = connection.CreateCommand();
            command.CommandText = <span class="hljs-string">$"SET LOCAL app.current_tenant = '<span class="hljs-subst">{tenantId}</span>';"</span>;
            <span class="hljs-keyword">await</span> command.ExecuteNonQueryAsync(cancellationToken);
        }

        <span class="hljs-keyword">await</span> <span class="hljs-keyword">base</span>.ConnectionOpenedAsync(connection, eventData, cancellationToken);
    }
}
</code></pre>
<h3 id="heading-4-wire-up-dependency-injection">4. Wire Up Dependency Injection</h3>
<pre><code class="lang-csharp">builder.Services.AddHttpContextAccessor();
builder.Services.AddScoped&lt;ITenantProvider, JwtTenantProvider&gt;();
builder.Services.AddScoped&lt;RlsConnectionInterceptor&gt;();

builder.Services.AddDbContext&lt;ApplicationDbContext&gt;((serviceProvider, options) =&gt;
{
    <span class="hljs-keyword">var</span> interceptor = serviceProvider.GetRequiredService&lt;RlsConnectionInterceptor&gt;();

    options.UseNpgsql(connectionString)
           .AddInterceptors(interceptor);
});
</code></pre>
<h3 id="heading-5-write-clean-controllers">5. Write Clean Controllers</h3>
<p>Your controllers need zero tenant filtering logic:</p>
<pre><code class="lang-csharp">[<span class="hljs-meta">ApiController</span>]
[<span class="hljs-meta">Route(<span class="hljs-meta-string">"api/[controller]"</span>)</span>]
[<span class="hljs-meta">Authorize</span>]
<span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">ProjectsController</span> : <span class="hljs-title">ControllerBase</span>
{
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">readonly</span> ApplicationDbContext _context;
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">readonly</span> ITenantProvider _tenantProvider;

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">ProjectsController</span>(<span class="hljs-params">
        ApplicationDbContext context,
        ITenantProvider tenantProvider</span>)</span>
    {
        _context = context;
        _tenantProvider = tenantProvider;
    }

    [<span class="hljs-meta">HttpGet</span>]
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">async</span> Task&lt;ActionResult&lt;List&lt;Project&gt;&gt;&gt; GetProjects()
    {
        <span class="hljs-comment">// RLS filters automatically - no WHERE clause needed</span>
        <span class="hljs-keyword">return</span> Ok(<span class="hljs-keyword">await</span> _context.Projects.ToListAsync());
    }

    [<span class="hljs-meta">HttpPost</span>]
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">async</span> Task&lt;ActionResult&lt;Project&gt;&gt; CreateProject(CreateProjectRequest request)
    {
        <span class="hljs-keyword">var</span> tenantId = _tenantProvider.GetCurrentTenantId();
        <span class="hljs-keyword">if</span> (<span class="hljs-keyword">string</span>.IsNullOrEmpty(tenantId))
            <span class="hljs-keyword">return</span> Unauthorized();

        <span class="hljs-keyword">var</span> project = <span class="hljs-keyword">new</span> Project
        {
            TenantId = Guid.Parse(tenantId),
            Name = request.Name,
            Description = request.Description
        };

        _context.Projects.Add(project);
        <span class="hljs-keyword">await</span> _context.SaveChangesAsync();

        <span class="hljs-keyword">return</span> CreatedAtAction(<span class="hljs-keyword">nameof</span>(GetProject), <span class="hljs-keyword">new</span> { id = project.Id }, project);
    }
}
</code></pre>
<h2 id="heading-critical-gotchas">Critical Gotchas</h2>
<p><strong>Use SET LOCAL, Not SET</strong>: <code>SET LOCAL</code> limits variables to transaction scope, preventing leakage across pooled connections. Using plain <code>SET</code> is dangerous with connection pooling.</p>
<p><strong>Index Your Tenant Columns</strong>: RLS policies execute on every query. Without proper indexes on tenant identifier columns, performance will suffer.</p>
<p><strong>Beware Superusers</strong>: PostgreSQL superusers bypass RLS by default. Use dedicated application roles with limited privileges.</p>
<p><strong>Test Thoroughly</strong>: Verify policies work as expected by attempting cross-tenant access with different user contexts.</p>
<h2 id="heading-the-bottom-line">The Bottom Line</h2>
<p>RLS moves security from application code into the database where it belongs. Your queries stay simple, your data stays isolated, and bugs in your filtering logic won't expose sensitive information. For multi-tenant applications, it's the difference between hoping your code is perfect and knowing your database enforces boundaries automatically.</p>
]]></content:encoded></item><item><title><![CDATA[Fluent Pipeline DSL in .NET – Turning Complex Workflows into Readable, Type-Safe Code]]></title><description><![CDATA[Introduction
Data-heavy back-end services—imports, ETL routines, validation workflows—often begin life as one long method full of if/else blocks. Soon the logic becomes brittle, difficult to test and almost impossible to extend. The Fluent Pipeline D...]]></description><link>https://pablorivas.eu/fluent-pipeline-dsl-in-net-turning-complex-workflows-into-readable-type-safe-code</link><guid isPermaLink="true">https://pablorivas.eu/fluent-pipeline-dsl-in-net-turning-complex-workflows-into-readable-type-safe-code</guid><category><![CDATA[C#]]></category><category><![CDATA[.NET]]></category><category><![CDATA[design patterns]]></category><dc:creator><![CDATA[Pablo Rivas Sánchez]]></dc:creator><pubDate>Sun, 03 Aug 2025 10:28:34 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1754442337389/3c49bfc5-5003-4de9-a90f-0e56601e36e4.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><strong>Introduction</strong></p>
<p>Data-heavy back-end services—imports, ETL routines, validation workflows—often begin life as one long method full of if/else blocks. Soon the logic becomes brittle, difficult to test and almost impossible to extend. The <strong>Fluent Pipeline DSL</strong> pattern offers a clean escape hatch: break the work into single-purpose steps (<em>handlers</em>), chain them with a fluent mini-language, and let the compiler guarantee that every piece fits.</p>
<h2 id="heading-below-we-explore-the-pattern-using-the-why-how-what-structure">Below we explore the pattern using the <strong>Why → How → What</strong> structure.</h2>
<h1 id="heading-why">WHY</h1>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Pain point</td><td>Consequence</td></tr>
</thead>
<tbody>
<tr>
<td><strong>Coupled logic</strong> – Each step knows too much about the others.</td><td>A change in one area ripples throughout the code base.</td></tr>
<tr>
<td><strong>Low testability</strong> – Flow control and business logic are intertwined.</td><td>Unit tests become huge, flaky, or are skipped altogether.</td></tr>
<tr>
<td><strong>Poor extensibility</strong> – Adding, removing, or reordering steps is risky.</td><td>Teams clone code, and the system degrades into a “big ball of mud.”</td></tr>
</tbody>
</table>
</div><p>A Fluent Pipeline solves these issues by:</p>
<ul>
<li><p><strong>Isolating</strong> each action into a self-contained handler.</p>
</li>
<li><p><strong>Composing</strong> handlers in a clear, linear pipeline.</p>
</li>
<li><p><strong>Enforcing type safety</strong> so that incompatible steps fail at compile-time.</p>
</li>
<li><p><strong>Exposing a DSL</strong> that reads like a specification of the business process.</p>
</li>
</ul>
<hr />
<h2 id="heading-how"><strong>HOW</strong></h2>
<p><strong>Contract per step</strong></p>
<pre><code class="lang-csharp"> <span class="hljs-keyword">public</span> <span class="hljs-keyword">interface</span> <span class="hljs-title">IHandler</span>&lt;<span class="hljs-keyword">in</span> <span class="hljs-title">TIn</span>, <span class="hljs-title">TOut</span>&gt;
{
    <span class="hljs-function">Task&lt;TOut&gt; <span class="hljs-title">HandleAsync</span>(<span class="hljs-params">
        TIn input,
        ProcessingContext ctx,
        CancellationToken ct = <span class="hljs-keyword">default</span></span>)</span>;
}
</code></pre>
<p><strong>Minimal engine</strong></p>
<pre><code class="lang-csharp"><span class="hljs-keyword">public</span> <span class="hljs-keyword">sealed</span> <span class="hljs-keyword">class</span> <span class="hljs-title">PipelineEngine</span>
{
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">readonly</span> IServiceProvider _sp;
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">PipelineEngine</span>(<span class="hljs-params">IServiceProvider sp</span>)</span> =&gt; _sp = sp;

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">Task</span>&lt;<span class="hljs-title">TOut</span>&gt; <span class="hljs-title">RunAsync</span>&lt;<span class="hljs-title">TH</span>, <span class="hljs-title">TIn</span>, <span class="hljs-title">TOut</span>&gt;(<span class="hljs-params">

        TIn input, ProcessingContext ctx, CancellationToken ct = <span class="hljs-keyword">default</span></span>)

        <span class="hljs-keyword">where</span> TH : class, IHandler&lt;TIn, TOut&gt;</span>

        =&gt; _sp.GetRequiredService&lt;TH&gt;().HandleAsync(input, ctx, ct);
}
</code></pre>
<p><strong>Fluent builder + DSL</strong></p>
<pre><code class="lang-csharp"><span class="hljs-keyword">public</span> <span class="hljs-keyword">sealed</span> <span class="hljs-keyword">class</span> <span class="hljs-title">PipelineBuilder</span>&lt;<span class="hljs-title">TCur</span>&gt;
{
    <span class="hljs-comment">/* … holds engine, current Task, context, token … */</span>

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">PipelineBuilder</span>&lt;<span class="hljs-title">TNext</span>&gt; <span class="hljs-title">Then</span>&lt;<span class="hljs-title">TH</span>, <span class="hljs-title">TNext</span>&gt;(<span class="hljs-params"></span>)

        <span class="hljs-keyword">where</span> TH : class, IHandler&lt;TCur, TNext&gt;</span>     { <span class="hljs-comment">/* chain logic */</span> }

    <span class="hljs-function"><span class="hljs-keyword">public</span> Task&lt;TCur&gt; <span class="hljs-title">FinishAsync</span>(<span class="hljs-params"></span>)</span> =&gt; _currentTask;

}

<span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">class</span> <span class="hljs-title">PipelineDsl</span>
{
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-title">PipelineBuilder</span>&lt;<span class="hljs-title">TOut</span>&gt; <span class="hljs-title">Execute</span>&lt;<span class="hljs-title">TH</span>, <span class="hljs-title">TIn</span>, <span class="hljs-title">TOut</span>&gt;(<span class="hljs-params">…</span>)</span>;
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-title">PipelineBuilder</span>&lt;<span class="hljs-title">TNext</span>&gt; <span class="hljs-title">Then</span>&lt;<span class="hljs-title">TH</span>, <span class="hljs-title">TCur</span>, <span class="hljs-title">TNext</span>&gt;(<span class="hljs-params">…</span>)</span>;
}
</code></pre>
<p><strong>Handlers are plain services</strong></p>
<pre><code class="lang-csharp"><span class="hljs-keyword">public</span> <span class="hljs-keyword">sealed</span> <span class="hljs-keyword">class</span> <span class="hljs-title">XmlValidation</span> : <span class="hljs-title">IHandler</span>&lt;<span class="hljs-title">string</span>, <span class="hljs-title">string</span>&gt; { … }
<span class="hljs-keyword">public</span> <span class="hljs-keyword">sealed</span> <span class="hljs-keyword">class</span> <span class="hljs-title">XmlParsing</span>   : <span class="hljs-title">IHandler</span>&lt;<span class="hljs-title">string</span>, <span class="hljs-title">ParsedDto</span>&gt; { … }
<span class="hljs-keyword">public</span> <span class="hljs-keyword">sealed</span> <span class="hljs-keyword">class</span> <span class="hljs-title">SaveToDb</span>     : <span class="hljs-title">IHandler</span>&lt;<span class="hljs-title">ParsedDto</span>, <span class="hljs-title">ImportSummary</span>&gt; { … }
</code></pre>
<p><strong>Orchestrate with MediatR (or any caller)</strong></p>
<pre><code class="lang-csharp"> <span class="hljs-keyword">var</span> summary = <span class="hljs-keyword">await</span> _engine
 .Execute&lt;XmlValidation, <span class="hljs-keyword">string</span>, <span class="hljs-keyword">string</span>&gt;(xml, ctx)
 .Then&lt;XmlParsing,   <span class="hljs-keyword">string</span>, ParsedDto&gt;()
 .Then&lt;SaveToDb,     ParsedDto, ImportSummary&gt;()
 .FinishAsync();
</code></pre>
<p>The pipeline is <strong>lazy</strong>—nothing runs until await. Cross-cutting concerns (logging, metrics, retries) can wrap each Task centrally without touching individual handlers.</p>
<h2 id="heading-what-benefits-amp-typical-use-cases">WHAT – Benefits &amp; Typical Use-Cases</h2>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Benefit</td><td>Why it matters in .NET back-ends</td></tr>
</thead>
<tbody>
<tr>
<td><strong>Extreme modularity</strong></td><td>Swap, insert, or remove steps by changing one line.</td></tr>
<tr>
<td><strong>Compile-time safety</strong></td><td>Generic signatures catch incompatible chains early.</td></tr>
<tr>
<td><strong>First-class testing</strong></td><td>Handlers test in isolation; entire pipelines test with mocks.</td></tr>
<tr>
<td><strong>Clear observability</strong></td><td>Builder can decorate each step for timing and tracing.</td></tr>
<tr>
<td><strong>Seamless with CQRS / Background jobs</strong></td><td>Command handlers become simple orchestrators; a job scheduler (TickerQ, Hangfire, Azure Queue) triggers the command but stays unaware of the inner flow.</td></tr>
</tbody>
</table>
</div><h3 id="heading-external-analogy-image-processing-in-python">External analogy – Image processing in Python</h3>
<p>A similar pattern is common in data science pipelines, e.g., <code>Pillow</code> handlers: <code>validate → resize → convert → upload</code>. Each step receives an image object, transforms it, and the fluent chain expresses the workflow as readable code—exactly the same idea we bring to .NET.</p>
<blockquote>
<p>Use the Fluent Pipeline DSL whenever you have a <strong>sequence of business transformations</strong> that should be <strong>easy to extend, test, and reason about</strong>. Your future self—and your teammates—will thank you.</p>
</blockquote>
]]></content:encoded></item><item><title><![CDATA[Seamless Navigation in Angular: Pre-fetching Data with Route Resolvers and NgRx SignalStore]]></title><description><![CDATA[In modern web applications, the user experience during navigation is paramount. A common yet jarring experience is navigating to a new page only to be greeted by a loading spinner before the content appears. This article explains how to eliminate thi...]]></description><link>https://pablorivas.eu/seamless-navigation-in-angular-pre-fetching-data-with-route-resolvers-and-ngrx-signalstore</link><guid isPermaLink="true">https://pablorivas.eu/seamless-navigation-in-angular-pre-fetching-data-with-route-resolvers-and-ngrx-signalstore</guid><category><![CDATA[Angular]]></category><category><![CDATA[NgRx]]></category><category><![CDATA[signalstore]]></category><category><![CDATA[State Management ]]></category><category><![CDATA[routing]]></category><category><![CDATA[user experience]]></category><category><![CDATA[performance]]></category><dc:creator><![CDATA[Pablo Rivas Sánchez]]></dc:creator><pubDate>Fri, 11 Jul 2025 22:10:49 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1752271492647/d38c1800-ac4d-4ef0-b375-0d0ec635e332.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>In modern web applications, the user experience during navigation is paramount. A common yet jarring experience is navigating to a new page only to be greeted by a loading spinner before the content appears. This article explains how to eliminate this pattern in Angular by pre-fetching data <em>before</em> a route is activated, ensuring your components are fully hydrated the moment they render.</p>
<p>We will achieve this using a powerful combination: the declarative state management of <strong>NgRx SignalStore</strong> and Angular's built-in <strong>Route Resolvers</strong>.</p>
<h3 id="heading-the-what-the-problem-of-in-component-data-fetching"><strong>The What: The Problem of In-Component Data Fetching</strong></h3>
<p>First, let's define the problem we are solving. The most common pattern for fetching data in Angular is to do it within a component's ngOnInit lifecycle hook.</p>
<ol>
<li><p>A user clicks a link to navigate to /products.</p>
</li>
<li><p>The Angular Router immediately destroys the old component and renders the ProductsComponent.</p>
</li>
<li><p>The ProductsComponent's view appears, but it's empty. It shows a loading spinner or a blank space.</p>
</li>
<li><p>Inside ngOnInit, a service method is called to fetch the products from an API.</p>
</li>
<li><p>Once the HTTP request completes, the data is populated, and the component's view updates to display the products.</p>
</li>
</ol>
<p>This sequence creates a noticeable "flicker" or "layout shift" where the user sees an incomplete view before the final content is displayed. Our goal is to change this sequence so that the data is ready <em>before</em> the component even exists.</p>
<h3 id="heading-the-why-the-superiority-of-the-resolver-pattern"><strong>The Why: The Superiority of the Resolver Pattern</strong></h3>
<p>Why should we move data fetching logic out of the component and into the routing layer? The benefits are significant and lead to a more robust and professional application architecture.</p>
<ol>
<li><p><strong>Flawless User Experience:</strong> The primary advantage. The user navigates, and the new view appears instantly complete and fully rendered. The wait time is spent during the route transition itself (which can be indicated by a global progress bar, like on YouTube or GitHub), not within a half-rendered component.</p>
</li>
<li><p><strong>Smarter Architecture, "Dumber" Components:</strong> Components become simpler and more focused. Their only job is to display the state that is given to them. They become agnostic of <em>how</em> or <em>when</em> the data is fetched. This greatly simplifies their logic and makes them easier to test and reuse.</p>
</li>
<li><p><strong>Centralized Data Fetching Logic:</strong> By using a Resolver, you tie the data requirement to the route itself. This centralizes your data-loading logic within your routing configuration, making it clear what data is needed for which part of your application.</p>
</li>
<li><p><strong>Robust Error Handling:</strong> What if the API call fails? With a Resolver, you can gracefully cancel the navigation. The user stays on the current page, and you can display a global error notification. This is much cleaner than navigating to a broken page that is stuck in a permanent loading state.</p>
</li>
</ol>
<h3 id="heading-the-how-a-step-by-step-implementation"><strong>The How: A Step-by-Step Implementation</strong></h3>
<p>Let's build this pattern step-by-step using SignalStore and a Route Resolver.</p>
<h4 id="heading-step-1-the-signalstore-with-a-robust-asynchronous-method"><strong>Step 1: The SignalStore with a Robust Asynchronous Method</strong></h4>
<p>First, we need a SignalStore with a method that both performs the data fetch and returns an Observable. The Angular Router will use this Observable to wait for the operation to complete.</p>
<pre><code><span class="hljs-comment">// src/app/todos.store.ts</span>
<span class="hljs-keyword">import</span> { inject } <span class="hljs-keyword">from</span> <span class="hljs-string">'@angular/core'</span>;
<span class="hljs-keyword">import</span> { patchState, signalStore, withMethods } <span class="hljs-keyword">from</span> <span class="hljs-string">'@ngrx/signals'</span>;
<span class="hljs-keyword">import</span> { withEntities, setAllEntities } <span class="hljs-keyword">from</span> <span class="hljs-string">'@ngrx/signals/entities'</span>;
<span class="hljs-keyword">import</span> { tapResponse } <span class="hljs-keyword">from</span> <span class="hljs-string">'@ngrx/operators'</span>;
<span class="hljs-keyword">import</span> { Observable } <span class="hljs-keyword">from</span> <span class="hljs-string">'rxjs'</span>;
<span class="hljs-keyword">import</span> { TodosService, Todo } <span class="hljs-keyword">from</span> <span class="hljs-string">'./todos.service'</span>; <span class="hljs-comment">// Your HTTP service</span>

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> TodosStore = signalStore(
  { <span class="hljs-attr">providedIn</span>: <span class="hljs-string">'root'</span> },
  <span class="hljs-comment">// Use withEntities for powerful collection management</span>
  withEntities&lt;Todo&gt;(),
  withMethods(<span class="hljs-function">(<span class="hljs-params">store, todosService = inject(TodosService)</span>) =&gt;</span> ({
    <span class="hljs-comment">// This is the method we will call from our Resolver.</span>
    <span class="hljs-comment">// Crucially, it returns an Observable that the router can wait for.</span>
    loadTodos(): Observable&lt;Todo[]&gt; {
      <span class="hljs-keyword">return</span> todosService.getTodos().pipe(
        <span class="hljs-comment">// tapResponse is a clean way to handle side effects like state updates</span>
        tapResponse({
          <span class="hljs-comment">// On success, update the store's state with the fetched entities</span>
          <span class="hljs-attr">next</span>: <span class="hljs-function">(<span class="hljs-params">todos</span>) =&gt;</span> patchState(store, setAllEntities(todos)),
          <span class="hljs-comment">// You can also handle errors here without breaking the stream</span>
          <span class="hljs-attr">error</span>: <span class="hljs-built_in">console</span>.error,
        })
      );
    },
  }))
);
</code></pre><h4 id="heading-step-2-create-the-route-resolver-function"><strong>Step 2: Create the Route Resolver Function</strong></h4>
<p>A Resolver is a simple, exportable function that acts as a "gatekeeper" for a route. The router executes this function and waits for the Observable it returns to complete before activating the route.</p>
<pre><code><span class="hljs-comment">// src/app/todos.resolver.ts</span>
<span class="hljs-keyword">import</span> { inject } <span class="hljs-keyword">from</span> <span class="hljs-string">'@angular/core'</span>;
<span class="hljs-keyword">import</span> { ResolveFn } <span class="hljs-keyword">from</span> <span class="hljs-string">'@angular/router'</span>;
<span class="hljs-keyword">import</span> { TodosStore } <span class="hljs-keyword">from</span> <span class="hljs-string">'./todos.store'</span>;
<span class="hljs-keyword">import</span> { Observable } <span class="hljs-keyword">from</span> <span class="hljs-string">'rxjs'</span>;

<span class="hljs-comment">// The resolver's return type is now Observable&lt;any&gt;</span>
<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> todosResolver: ResolveFn&lt;any&gt; = <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-keyword">const</span> store = inject(TodosStore);

  <span class="hljs-comment">// Call the store method AND RETURN the resulting Observable.</span>
  <span class="hljs-comment">// The router will now pause navigation and wait for this to complete.</span>
  <span class="hljs-keyword">return</span> store.loadTodos(); 
};
</code></pre><h4 id="heading-step-3-configure-the-route"><strong>Step 3: Configure the Route</strong></h4>
<p>Now, we connect our todosResolver to a specific route in our application's routing configuration.</p>
<pre><code><span class="hljs-comment">// src/app/app.routes.ts</span>
<span class="hljs-keyword">import</span> { Routes } <span class="hljs-keyword">from</span> <span class="hljs-string">'@angular/router'</span>;
<span class="hljs-keyword">import</span> { TodosComponent } <span class="hljs-keyword">from</span> <span class="hljs-string">'./todos/todos.component'</span>;
<span class="hljs-keyword">import</span> { todosResolver } <span class="hljs-keyword">from</span> <span class="hljs-string">'./todos.resolver'</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> routes: Routes = [
  {
    <span class="hljs-attr">path</span>: <span class="hljs-string">'todos'</span>,
    <span class="hljs-attr">component</span>: TodosComponent,
    <span class="hljs-comment">// Add the `resolve` property here</span>
    <span class="hljs-attr">resolve</span>: {
      <span class="hljs-comment">// The key 'todos' is arbitrary. The value is our resolver function.</span>
      <span class="hljs-attr">todos</span>: todosResolver,
    },
  },
  <span class="hljs-comment">// ... other routes</span>
];
</code></pre><h4 id="heading-step-4-the-simplified-component"><strong>Step 4: The Simplified Component</strong></h4>
<p>This is the payoff. Look how clean and simple our TodosComponent becomes. It has no ngOnInit for fetching data, no loading state management, and no complex logic. It trusts that when it is rendered, the TodosStore already contains the necessary state.</p>
<pre><code class="lang-plaintext">// src/app/todos/todos.component.ts
import { Component, inject } from '@angular/core';
import { TodosStore } from '../todos.store';

@Component({
  selector: 'app-todos',
  standalone: true,
  // imports: [/* Add necessary Angular imports here, like @for */],
  template: `
    &lt;h2&gt;My Todos&lt;/h2&gt;

    &lt;!-- 
      No need for @if (store.isLoading())!
      By the time this component renders, the data is already here,
      because the resolver waited for the loadTodos() call to complete.
    --&gt;

    &lt;ul&gt;
      @for (todo of store.entities(); track todo.id) {
        &lt;li&gt;{{ todo.title }}&lt;/li&gt;
      } @empty {
        &lt;li&gt;No todos found.&lt;/li&gt;
      }
    &lt;/ul&gt;
  `,
})
export class TodosComponent {
  // Simply inject the store and read from it.
  readonly store = inject(TodosStore);
}
</code></pre>
<h3 id="heading-conclusion"><strong>Conclusion</strong></h3>
<p>By combining NgRx SignalStore with Angular's Route Resolvers, you can fundamentally improve your application's architecture and user experience. This pattern moves data-fetching logic to a more appropriate location, simplifies your components, and provides a seamless, professional navigation flow that users expect from high-quality applications. The next time you find yourself adding a loading spinner inside a component, consider promoting that logic to a Resolver and let the Angular Router do the heavy lifting for you.</p>
]]></content:encoded></item><item><title><![CDATA[Mastering Domain Events Made Simple with MediatR in C# and .NET 8]]></title><description><![CDATA[Readers of this article will gain insights into mastering domain events with Meditr in C# and .NET 8. They'll learn how to effectively manage domain events, from creation to handling, using this powerful library. Through step-by-step guidance, they'l...]]></description><link>https://pablorivas.eu/mastering-domain-events-made-simple-with-mediatr-in-c-and-net-8</link><guid isPermaLink="true">https://pablorivas.eu/mastering-domain-events-made-simple-with-mediatr-in-c-and-net-8</guid><category><![CDATA[#entityframeworkcore]]></category><category><![CDATA[MediatR]]></category><category><![CDATA[#Domain-Driven-Design]]></category><dc:creator><![CDATA[Pablo Rivas Sánchez]]></dc:creator><pubDate>Sun, 19 May 2024 23:57:10 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1716163015485/7caec1f6-5987-4edc-a4e7-2624c526cd69.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Readers of this article will gain insights into mastering <strong>domain events</strong> with <strong>Meditr</strong> in <strong>C#</strong> and <strong>.NET 8</strong>. They'll learn how to effectively manage <strong>domain events</strong>, from <strong>creation</strong> to <strong>handling</strong>, using this powerful library. Through step-by-step guidance, they'll discover practical techniques for optimizing <strong>event-driven architectures</strong>, enhancing <strong>scalability</strong> and <strong>maintainability</strong> in their software projects. To structure our discussion, we will use the <strong>What? Why? How?</strong> framework.</p>
<h3 id="heading-what"><strong>What?</strong></h3>
<p>Domain events are a design pattern in <strong>Domain-Driven Design (DDD)</strong> used to capture and broadcast significant state changes in your domain model. In the context of <strong>C#</strong> with <strong>Entity Framework Core 8</strong> and <strong>MediatR</strong>, domain events enable you to decouple the core logic of your application from side effects, such as notifications or external system updates.</p>
<p><strong>MediatR</strong> is a popular library in the .NET ecosystem that facilitates <strong>CQRS (Command Query Responsibility Segregation)</strong> and <strong>mediator patterns</strong>. It helps to manage interactions between objects, reducing dependencies and promoting a clean, maintainable codebase. MediatR uses handlers to process requests, such as commands, queries, and notifications, allowing for better separation of concerns.</p>
<h3 id="heading-why"><strong>Why?</strong></h3>
<p><strong>Separation of Concerns:</strong> By using domain events, you ensure that your core <strong>domain logic</strong> remains clean and focused on <strong>business rules</strong>, while other concerns (like <strong>notifications</strong> or <strong>logging</strong>) are handled separately.</p>
<p><strong>Decoupling:</strong> Domain events help to decouple different parts of your application, promoting a more <strong>modular</strong> and <strong>maintainable architecture</strong>.</p>
<p><strong>Testability:</strong> With domain events, you can more easily <strong>test</strong> your core domain logic without worrying about <strong>side effects</strong>, improving the overall <strong>testability</strong> of your application.</p>
<h3 id="heading-how"><strong>How?</strong></h3>
<p>To implement domain events using <strong>C#</strong> with <strong>Entity Framework Core 8</strong> and <strong>MediatR</strong>, follow these steps:</p>
<p><strong>Capturing Domain Events</strong></p>
<h4 id="heading-1-define-a-basic-interface-for-domain-events">1. <strong>Define a Basic Interface for Domain Events:</strong></h4>
<p>    Create an interface <code>IDomainEvent</code> that inherits from MediatR's <code>INotification</code>. This interface will represent the domain events.</p>
<pre><code class="lang-csharp">    <span class="hljs-keyword">namespace</span> <span class="hljs-title">RecipeManagement.Domain</span>
    {
        <span class="hljs-keyword">using</span> MediatR;

        <span class="hljs-keyword">public</span> <span class="hljs-keyword">interface</span> <span class="hljs-title">IDomainEvent</span> : <span class="hljs-title">INotification</span> { }
    }
</code></pre>
<h4 id="heading-2-create-a-base-entity-class">2. <strong>Create a Base Entity Class:</strong></h4>
<p>    Develop a base entity class that all your entities can inherit from. This class will include:</p>
<ul>
<li><p>A list of <code>IDomainEvent</code> called <code>DomainEvents</code> to capture messages we want to publish.</p>
</li>
<li><p>A primary key property called <code>Id</code> for entity identification.</p>
</li>
<li><p>A method <code>QueueDomainEvent</code> to add events to the <code>DomainEvents</code> list.</p>
</li>
</ul>
<pre><code class="lang-csharp">    <span class="hljs-keyword">namespace</span> <span class="hljs-title">RecipeManagement.Domain</span>
    {
        <span class="hljs-keyword">using</span> System;
        <span class="hljs-keyword">using</span> System.Collections.Generic;
        <span class="hljs-keyword">using</span> System.ComponentModel.DataAnnotations;
        <span class="hljs-keyword">using</span> System.ComponentModel.DataAnnotations.Schema;

        <span class="hljs-keyword">public</span> <span class="hljs-keyword">abstract</span> <span class="hljs-keyword">class</span> <span class="hljs-title">BaseEntity</span>
        {
            [<span class="hljs-meta">Key</span>]
            <span class="hljs-keyword">public</span> Guid Id { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">private</span> <span class="hljs-keyword">set</span>; } = Guid.NewGuid();

            [<span class="hljs-meta">NotMapped</span>]
            <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">List</span>&lt;<span class="hljs-title">IDomainEvent</span>&gt; DomainEvents</span> { <span class="hljs-keyword">get</span>; } = <span class="hljs-keyword">new</span> List&lt;IDomainEvent&gt;();

            <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">QueueDomainEvent</span>(<span class="hljs-params">IDomainEvent @<span class="hljs-keyword">event</span></span>)</span>
            {
                DomainEvents.Add(@event);
            }
        }
    }
</code></pre>
<h4 id="heading-3-define-and-publish-the-domain-event">3. <strong>Define and Publish the Domain Event:</strong></h4>
<p>    Create a domain event class that implements <code>IDomainEvent</code>. Raise this event in your domain model.</p>
<pre><code class="lang-csharp">    <span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">OrderPlacedEvent</span> : <span class="hljs-title">IDomainEvent</span>
    {
        <span class="hljs-keyword">public</span> <span class="hljs-keyword">int</span> OrderId { <span class="hljs-keyword">get</span>; }
        <span class="hljs-keyword">public</span> DateTime OrderDate { <span class="hljs-keyword">get</span>; }

        <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">OrderPlacedEvent</span>(<span class="hljs-params"><span class="hljs-keyword">int</span> orderId, DateTime orderDate</span>)</span>
        {
            OrderId = orderId;
            OrderDate = orderDate;
        }
    }

    <span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">Order</span> : <span class="hljs-title">BaseEntity</span>
    {
        <span class="hljs-keyword">public</span> DateTime OrderDate { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">private</span> <span class="hljs-keyword">set</span>; }

        <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">PlaceOrder</span>(<span class="hljs-params">DateTime orderDate</span>)</span>
        {
            OrderDate = orderDate;
            QueueDomainEvent(<span class="hljs-keyword">new</span> OrderPlacedEvent(Id, orderDate));
        }
    }
</code></pre>
<h4 id="heading-4-save-and-dispatch-domain-events">4. <strong>Save and Dispatch Domain Events:</strong></h4>
<p>    Customize your <code>DbContext</code> to intercept the <code>SaveChanges</code> and <code>SaveChangesAsync</code> methods and dispatch the domain events after the changes are committed by encapsulating the dispatch logic in a method called <code>DispatchDomainEvents</code>.</p>
<pre><code class="lang-csharp">    <span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">AppDbContext</span> : <span class="hljs-title">DbContext</span>
    {
        <span class="hljs-keyword">private</span> <span class="hljs-keyword">readonly</span> IMediator _mediator;

        <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">AppDbContext</span>(<span class="hljs-params">DbContextOptions&lt;AppDbContext&gt; options, IMediator mediator</span>)
            : <span class="hljs-title">base</span>(<span class="hljs-params">options</span>)</span>
        {
            _mediator = mediator;
        }

        <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">override</span> <span class="hljs-keyword">int</span> <span class="hljs-title">SaveChanges</span>(<span class="hljs-params"></span>)</span>
        {
            OnBeforeSaving().GetAwaiter().GetResult();
            <span class="hljs-keyword">return</span> <span class="hljs-keyword">base</span>.SaveChanges();
        }

        <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">override</span> <span class="hljs-keyword">async</span> Task&lt;<span class="hljs-keyword">int</span>&gt; <span class="hljs-title">SaveChangesAsync</span>(<span class="hljs-params">CancellationToken cancellationToken = <span class="hljs-keyword">default</span></span>)</span>
        {
            <span class="hljs-keyword">await</span> OnBeforeSaving();
            <span class="hljs-keyword">return</span> <span class="hljs-keyword">await</span> <span class="hljs-keyword">base</span>.SaveChangesAsync(cancellationToken);
        }

        <span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-keyword">async</span> Task <span class="hljs-title">OnBeforeSaving</span>(<span class="hljs-params"></span>)</span>
        {
            <span class="hljs-keyword">await</span> DispatchDomainEvents();
        }

        <span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-keyword">async</span> Task <span class="hljs-title">DispatchDomainEvents</span>(<span class="hljs-params"></span>)</span>
        {
            <span class="hljs-keyword">var</span> domainEventEntities = ChangeTracker.Entries&lt;BaseEntity&gt;()
                .Select(po =&gt; po.Entity)
                .Where(po =&gt; po.DomainEvents.Any())
                .ToArray();

            <span class="hljs-keyword">foreach</span> (<span class="hljs-keyword">var</span> entity <span class="hljs-keyword">in</span> domainEventEntities)
            {
                <span class="hljs-keyword">var</span> events = entity.DomainEvents.ToArray();
                entity.DomainEvents.Clear();
                <span class="hljs-keyword">foreach</span> (<span class="hljs-keyword">var</span> entityDomainEvent <span class="hljs-keyword">in</span> events)
                {
                    <span class="hljs-keyword">await</span> _mediator.Publish(entityDomainEvent);
                }
            }
        }
    }
</code></pre>
<h4 id="heading-5-handle-the-domain-event">5. <strong>Handle the Domain Event:</strong></h4>
<p>    Implement a handler for your domain event. The handler should implement the <code>INotificationHandler</code> interface from MediatR.</p>
<pre><code class="lang-csharp">    <span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">OrderPlacedEventHandler</span> : <span class="hljs-title">INotificationHandler</span>&lt;<span class="hljs-title">OrderPlacedEvent</span>&gt;
    {
        <span class="hljs-function"><span class="hljs-keyword">public</span> Task <span class="hljs-title">Handle</span>(<span class="hljs-params">OrderPlacedEvent notification, CancellationToken cancellationToken</span>)</span>
        {
            <span class="hljs-comment">// Handle the event (e.g., send an email notification)</span>
            Console.WriteLine(<span class="hljs-string">$"Order placed with ID: <span class="hljs-subst">{notification.OrderId}</span> on <span class="hljs-subst">{notification.OrderDate}</span>"</span>);
            <span class="hljs-keyword">return</span> Task.CompletedTask;
        }
    }
</code></pre>
<h4 id="heading-6-register-dependencies">6. <strong>Register Dependencies:</strong></h4>
<p>    Finally, register MediatR and your handlers in the dependency injection container.</p>
<pre><code class="lang-csharp">    <span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">Startup</span>
    {
        <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">ConfigureServices</span>(<span class="hljs-params">IServiceCollection services</span>)</span>
        {
            services.AddDbContext&lt;AppDbContext&gt;(options =&gt; 
                options.UseSqlServer(Configuration.GetConnectionString(<span class="hljs-string">"DefaultConnection"</span>)));

            services.AddMediatR(<span class="hljs-keyword">typeof</span>(Startup).Assembly);
            <span class="hljs-comment">// Register other services and handlers</span>
        }
    }
</code></pre>
<p>By following these steps, you can effectively implement <strong>domain events</strong> in your <strong>C#</strong> application using <strong>Entity Framework Core 8</strong> and <strong>MediatR</strong>. This approach helps you maintain a clean <strong>separation of concerns</strong>, decouple your application's components, and improve <strong>testability</strong>.</p>
]]></content:encoded></item><item><title><![CDATA[Enhancing Database Operations with EF Core 7 Interceptors]]></title><description><![CDATA[Entity Framework Core 7 (EF7) introduces a range of enhancements, including advanced support for interceptors. Interceptors empower developers to intercept, modify, and suppress EF Core operations, offering granular control over database interactions...]]></description><link>https://pablorivas.eu/enhancing-database-operations-with-ef-core-7-interceptors</link><guid isPermaLink="true">https://pablorivas.eu/enhancing-database-operations-with-ef-core-7-interceptors</guid><category><![CDATA[efcore]]></category><category><![CDATA[C#]]></category><category><![CDATA[Databases]]></category><dc:creator><![CDATA[Pablo Rivas Sánchez]]></dc:creator><pubDate>Sun, 24 Mar 2024 10:27:41 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1711275984163/d9de2047-7b42-4c58-a90d-09465556e078.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Entity Framework Core 7 (EF7) introduces a range of enhancements, including advanced support for interceptors. Interceptors empower developers to intercept, modify, and suppress EF Core operations, offering granular control over database interactions. In this article, we'll explore the practical applications of interceptors in real-world scenarios, focusing on a new example of suppressing errors when deleting entities with no results.</p>
<p><strong>Understanding Interceptors:</strong><br />Interceptors serve as middleware within EF Core, enabling developers to intercept various stages of database operations. From creating and modifying entity instances to handling concurrency conflicts, interceptors provide flexibility and control over EF Core's behavior.</p>
<p><strong>New and Enhanced Interceptors:</strong><br />EF7 introduces several enhanced interceptors, allowing developers to:</p>
<ol>
<li><p>Materialization Interception: Modify entity instances during creation, facilitating tasks such as setting unmapped properties or injecting services.</p>
</li>
<li><p>Query Expression Interception: Modify LINQ expression trees before compilation, ensuring stable ordering and enhancing query performance.</p>
</li>
<li><p>Concurrency Handling Interception: Provide mechanisms to handle optimistic concurrency conflicts, allowing graceful conflict resolution.</p>
</li>
</ol>
<p><strong>Practical Example: Suppressing Errors on Entity Deletion:</strong><br />Consider a scenario where multiple requests attempt to delete the same entity simultaneously. In EF Core 7, we can use interceptors to suppress errors when deleting entities with no results, ensuring smooth application operation even in concurrent scenarios.</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">SuppressDeleteErrorInterceptor</span> : <span class="hljs-title">ISaveChangesInterceptor</span>
{
    <span class="hljs-function"><span class="hljs-keyword">public</span> InterceptionResult&lt;<span class="hljs-keyword">int</span>&gt; <span class="hljs-title">SavingChanges</span>(<span class="hljs-params">DbContextEventData eventData, InterceptionResult&lt;<span class="hljs-keyword">int</span>&gt; result</span>)</span>
    {
        <span class="hljs-keyword">var</span> deletedEntries = eventData.Context.ChangeTracker.Entries()
            .Where(e =&gt; e.State == EntityState.Deleted &amp;&amp; !e.Metadata.IsOwned());

        <span class="hljs-keyword">if</span> (deletedEntries.Any())
        {
            <span class="hljs-keyword">var</span> nonDeletedEntriesCount = eventData.Context.ChangeTracker.Entries()
                .Count(e =&gt; e.State != EntityState.Deleted &amp;&amp; !e.Metadata.IsOwned());

            <span class="hljs-comment">// Check if there are no non-deleted entries</span>
            <span class="hljs-keyword">if</span> (nonDeletedEntriesCount == <span class="hljs-number">0</span>)
            {
                Console.WriteLine(<span class="hljs-string">"Suppressing deletion error for entity without results."</span>);
                <span class="hljs-keyword">return</span> InterceptionResult&lt;<span class="hljs-keyword">int</span>&gt;.SuppressWithResult(<span class="hljs-number">0</span>); <span class="hljs-comment">// Suppress the deletion error</span>
            }
        }

        <span class="hljs-keyword">return</span> result;
    }
}
</code></pre>
<p><strong>Considerations and Best Practices:</strong><br />While interceptors offer significant flexibility, it's essential to use them judiciously and consider potential implications on application behavior. Ensure that interceptors align with application requirements and adhere to best practices to maintain code clarity and robustness.</p>
<p><strong>Avoiding Database Operation Errors:</strong><br />The <code>SuppressDeleteErrorInterceptor</code> introduced above effectively suppresses errors that may occur during entity deletion, such as the common error message: "Database operation expected to affect 1 row(s) but actually affected 0 row(s). Data may have been modified or deleted since entities were loaded." By implementing this interceptor, applications can gracefully handle scenarios where the expected deletion operation affects no rows, thus avoiding unnecessary error notifications.</p>
<p><strong>Conclusion:</strong><br />EF Core 7 interceptors provide powerful tools for customizing database interactions, enabling developers to address complex scenarios with ease. By leveraging interceptors effectively, developers can enhance application performance, maintain data integrity, and ensure smooth operation even in challenging concurrent scenarios.</p>
]]></content:encoded></item></channel></rss>