<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0">
<channel>
<title>Coder Legion - Recent posts tagged ef</title>
<link>https://coderlegion.com/tag/ef</link>
<description>Powered by Question2Answer</description>
<item>
<title>🚀 EF Core Query Optimization for Beginners</title>
<link>https://coderlegion.com/21593/ef-core-query-optimization-for-beginners</link>
<description>&lt;p&gt;When developers first start using Entity Framework Core, it's easy to write queries that work—but not all queries perform well.&lt;/p&gt;
&lt;p&gt;The good news? A few simple practices can make your queries &lt;strong&gt;much faster&lt;/strong&gt;, &lt;strong&gt;use less memory&lt;/strong&gt;, and &lt;strong&gt;generate better SQL&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;We'll use this real-world example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;var offerDtos = await db.Offers
    .AsNoTracking()
    .Where(o =&amp;gt; o.DeliveryDate &amp;gt;= date &amp;amp;&amp;amp;
                o.DeliveryDate &amp;lt; nextDay &amp;amp;&amp;amp;
                (o.IsCompleted == null || o.IsCompleted == 0))
    .OrderBy(o =&amp;gt; o.Id)
    .Select(o =&amp;gt; new OfferForWarehouseDto
    {
        OfferId = o.Id,
        CustomerName = o.Customer.Name,
        DeliveryDate = o.DeliveryDate,
        OfferDate = o.OfferDate,
        Status = o.Status,
        Comments = o.Comments,
        WarehouseStatus = &quot;Pending&quot;,
        Lines = o.OfferLines
            .Select(l =&amp;gt; new WarehouseLineDto
            {
                OfferLineId = l.Id,
                ItemCode = l.ItemCode,
                ItemDescription = l.ItemDescription,
                Color = l.Color,
                Unit = l.Unit,
                OrderedQuantity = l.Quantity,
                CurrentStock = l.Product.Stock
            })
            .ToList()
    })
    .ToListAsync(cancellationToken);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Let's break it down step by step.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;1-use-asnotracking-for-read-only-queries-&quot;&gt;1. Use &lt;code&gt;AsNoTracking()&lt;/code&gt; for Read-Only Queries ⭐&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;.AsNoTracking()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;One of EF Core's most useful features is the &lt;strong&gt;Change Tracker&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Whenever you load entities, EF Core tracks them in memory so it can detect changes and generate the appropriate &lt;code&gt;INSERT&lt;/code&gt;, &lt;code&gt;UPDATE&lt;/code&gt;, or &lt;code&gt;DELETE&lt;/code&gt; statements when you call &lt;code&gt;SaveChanges()&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;var offer = await db.Offers.FirstAsync();

offer.Status = &quot;Completed&quot;;

await db.SaveChangesAsync();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Even though you never tell EF Core that &lt;code&gt;Status&lt;/code&gt; changed, it knows because the &lt;strong&gt;Change Tracker&lt;/strong&gt; has been monitoring the entity.&lt;/p&gt;
&lt;p&gt;This is extremely convenient when you're editing data.&lt;/p&gt;
&lt;p&gt;However, tracking every entity comes with a cost.&lt;/p&gt;
&lt;p&gt;For each tracked entity, EF Core stores:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The original values&lt;/li&gt;
&lt;li&gt;The current values&lt;/li&gt;
&lt;li&gt;The entity state (&lt;code&gt;Added&lt;/code&gt;, &lt;code&gt;Modified&lt;/code&gt;, &lt;code&gt;Deleted&lt;/code&gt;, &lt;code&gt;Unchanged&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Relationship information&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you're only displaying data in a grid or returning it from an API, all of this tracking is unnecessary.&lt;/p&gt;
&lt;p&gt;That's why read-only queries should use:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;.AsNoTracking()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This tells EF Core:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;&quot;I'm only reading this data. Don't track it.&quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4 id=&quot;benefits&quot;&gt;Benefits&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;✅ Lower memory usage&lt;/li&gt;
&lt;li&gt;✅ Faster query execution&lt;/li&gt;
&lt;li&gt;✅ Less CPU overhead&lt;/li&gt;
&lt;li&gt;✅ Better performance for APIs, dashboards, and reports&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Learn more&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Change Tracking: &lt;a rel=&quot;nofollow&quot; href=&quot;https://learn.microsoft.com/en-us/ef/core/change-tracking/&quot;&gt;https://learn.microsoft.com/en-us/ef/core/change-tracking/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Tracking vs. No-Tracking Queries: &lt;a rel=&quot;nofollow&quot; href=&quot;https://learn.microsoft.com/en-us/ef/core/querying/tracking&quot;&gt;https://learn.microsoft.com/en-us/ef/core/querying/tracking&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id=&quot;2-filter-data-before-loading-it&quot;&gt;2. Filter Data Before Loading It&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;.Where(o =&amp;gt;
    o.DeliveryDate &amp;gt;= date &amp;amp;&amp;amp;
    o.DeliveryDate &amp;lt; nextDay &amp;amp;&amp;amp;
    (o.IsCompleted == null || o.IsCompleted == 0))
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Always filter data in the database.&lt;/p&gt;
&lt;p&gt;Good:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;db.Offers.Where(...)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Bad:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;db.Offers.ToList().Where(...)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The first generates a SQL &lt;code&gt;WHERE&lt;/code&gt; clause.&lt;/p&gt;
&lt;p&gt;The second loads the entire table into memory before filtering.&lt;/p&gt;
&lt;p&gt;The database is optimized for filtering large datasets, so let it do the work.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;3-project-only-what-you-need&quot;&gt;3. Project Only What You Need&lt;/h2&gt;
&lt;p&gt;Notice that we're not returning &lt;code&gt;Offer&lt;/code&gt; entities.&lt;/p&gt;
&lt;p&gt;Instead, we're creating DTOs.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;.Select(o =&amp;gt; new OfferForWarehouseDto
{
    OfferId = o.Id,
    CustomerName = o.Customer.Name
})
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This tells EF Core exactly which columns to retrieve.&lt;/p&gt;
&lt;p&gt;Instead of:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;SELECT *
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;EF Core generates SQL that selects only the properties used in the DTO.&lt;/p&gt;
&lt;p&gt;Benefits include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Smaller SQL results&lt;/li&gt;
&lt;li&gt;Less network traffic&lt;/li&gt;
&lt;li&gt;Lower memory usage&lt;/li&gt;
&lt;li&gt;Faster execution&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Learn more&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://learn.microsoft.com/en-us/ef/core/performance/efficient-querying#project-only-properties-you-need&quot;&gt;https://learn.microsoft.com/en-us/ef/core/performance/efficient-querying#project-only-properties-you-need&lt;/a&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;4-don-t-use-include-when-projecting&quot;&gt;4. Don't Use &lt;code&gt;Include()&lt;/code&gt; When Projecting&lt;/h2&gt;
&lt;p&gt;Many developers write:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;.Include(o =&amp;gt; o.Customer)
.Include(o =&amp;gt; o.OfferLines)
.ThenInclude(l =&amp;gt; l.Product)
.Select(...)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is unnecessary.&lt;/p&gt;
&lt;p&gt;When projecting into a DTO, EF Core automatically generates the required SQL joins.&lt;/p&gt;
&lt;p&gt;Simply write:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;.Select(...)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The generated SQL is usually smaller, cleaner, and more efficient.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;5-sort-in-sql&quot;&gt;5. Sort in SQL&lt;/h2&gt;
&lt;p&gt;Sorting should happen before materializing the results.&lt;/p&gt;
&lt;p&gt;Good:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;.OrderBy(o =&amp;gt; o.Id)
.ToListAsync();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Bad:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;.ToList()
.OrderBy(o =&amp;gt; o.Id);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;SQL Server is optimized for sorting data.&lt;/p&gt;
&lt;p&gt;Don't move that work into your application.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;6-project-child-collections&quot;&gt;6. Project Child Collections&lt;/h2&gt;
&lt;p&gt;EF Core can also project related collections.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;Lines = o.OfferLines
    .Select(l =&amp;gt; new WarehouseLineDto
    {
        ItemCode = l.ItemCode,
        CurrentStock = l.Product.Stock
    })
    .ToList()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Notice that we never load the entire &lt;code&gt;OfferLine&lt;/code&gt; or &lt;code&gt;Product&lt;/code&gt; entities.&lt;/p&gt;
&lt;p&gt;Only the required columns are selected.&lt;/p&gt;
&lt;p&gt;This keeps queries efficient while still returning nested objects.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;7-use-async-queries&quot;&gt;7. Use Async Queries&lt;/h2&gt;
&lt;p&gt;Always prefer:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;await ToListAsync();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;instead of:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;ToList();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Async queries don't make the database faster, but they prevent your application thread from blocking while waiting for SQL Server.&lt;/p&gt;
&lt;p&gt;This improves scalability in ASP.NET Core applications.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;8-create-proper-database-indexes&quot;&gt;8. Create Proper Database Indexes&lt;/h2&gt;
&lt;p&gt;Even the best LINQ query can be slow if the database has no indexes.&lt;/p&gt;
&lt;p&gt;This query filters by:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;DeliveryDate&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;IsCompleted&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;A composite index can dramatically improve performance.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;CREATE INDEX IX_Offers_DeliveryDate_IsCompleted
ON Offers (DeliveryDate, IsCompleted);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Remember:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;EF Core generates SQL.&lt;br&gt;
SQL Server executes SQL.&lt;br&gt;
Indexes make SQL fast.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id=&quot;final-optimized-query&quot;&gt;Final Optimized Query&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;var offerDtos = await db.Offers
    .AsNoTracking()
    .Where(o =&amp;gt; o.DeliveryDate &amp;gt;= date &amp;amp;&amp;amp;
                o.DeliveryDate &amp;lt; nextDay &amp;amp;&amp;amp;
                (o.IsCompleted == null || o.IsCompleted == 0))
    .OrderBy(o =&amp;gt; o.Id)
    .Select(o =&amp;gt; new OfferForWarehouseDto
    {
        OfferId = o.Id,
        CustomerName = o.Customer.Name,
        DeliveryDate = o.DeliveryDate,
        OfferDate = o.OfferDate,
        Status = o.Status,
        Comments = o.Comments,
        WarehouseStatus = &quot;Pending&quot;,
        Lines = o.OfferLines
            .Select(l =&amp;gt; new WarehouseLineDto
            {
                OfferLineId = l.Id,
                ItemCode = l.ItemCode,
                ItemDescription = l.ItemDescription,
                Color = l.Color,
                Unit = l.Unit,
                OrderedQuantity = l.Quantity,
                CurrentStock = l.Product.Stock
            })
            .ToList()
    })
    .ToListAsync(cancellationToken);
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;-key-takeaways&quot;&gt;🎯 Key Takeaways&lt;/h2&gt;
&lt;p&gt;Whenever you write an EF Core query, ask yourself:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;✅ Is this a read-only query? → Use &lt;code&gt;AsNoTracking()&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;✅ Am I letting the database filter my data?&lt;/li&gt;
&lt;li&gt;✅ Am I selecting only the columns I need?&lt;/li&gt;
&lt;li&gt;✅ Am I avoiding unnecessary &lt;code&gt;Include()&lt;/code&gt; calls?&lt;/li&gt;
&lt;li&gt;✅ Am I using async methods like &lt;code&gt;ToListAsync()&lt;/code&gt;?&lt;/li&gt;
&lt;li&gt;✅ Does my database have the right indexes?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Following these simple habits will help you write faster, cleaner, and more scalable EF Core applications.&lt;/p&gt;
&lt;h3 id=&quot;additional-resources&quot;&gt;Additional Resources&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;EF Core Performance: &lt;a rel=&quot;nofollow&quot; href=&quot;https://learn.microsoft.com/en-us/ef/core/performance/&quot;&gt;https://learn.microsoft.com/en-us/ef/core/performance/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Efficient Querying: &lt;a rel=&quot;nofollow&quot; href=&quot;https://learn.microsoft.com/en-us/ef/core/performance/efficient-querying&quot;&gt;https://learn.microsoft.com/en-us/ef/core/performance/efficient-querying&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Change Tracking: &lt;a rel=&quot;nofollow&quot; href=&quot;https://learn.microsoft.com/en-us/ef/core/change-tracking/&quot;&gt;https://learn.microsoft.com/en-us/ef/core/change-tracking/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Tracking vs. No-Tracking Queries: &lt;a rel=&quot;nofollow&quot; href=&quot;https://learn.microsoft.com/en-us/ef/core/querying/tracking&quot;&gt;https://learn.microsoft.com/en-us/ef/core/querying/tracking&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/21593/ef-core-query-optimization-for-beginners</guid>
<pubDate>Mon, 29 Jun 2026 19:47:13 +0000</pubDate>
</item>
<item>
<title>5 EF Core Features Every Enterprise Developer Should Know</title>
<link>https://coderlegion.com/19745/5-ef-core-features-every-enterprise-developer-should-know</link>
<description>&lt;p&gt;Entity Framework Core (EF Core) is often introduced as an ORM that simplifies data access, but its real value in enterprise applications comes from features that improve reliability, scalability, and maintainability. &lt;/p&gt;
&lt;p&gt;As applications grow and operate in cloud environments, these capabilities become essential rather than optional.&lt;/p&gt;
&lt;h3 id=&quot;1-connection-resiliency-and-retry-policies&quot;&gt;1. Connection Resiliency and Retry Policies&lt;/h3&gt;
&lt;p&gt;Enterprise systems frequently run on cloud-hosted databases where transient network failures, failovers, and throttling can occur. EF Core provides built-in connection resiliency by automatically retrying failed database operations.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;options.UseSqlServer(connectionString, sqlOptions =&amp;gt;
    sqlOptions.EnableRetryOnFailure(
        maxRetryCount: 5,
        maxRetryDelay: TimeSpan.FromSeconds(10),
        errorNumbersToAdd: null));
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This helps applications recover gracefully from temporary infrastructure issues without requiring custom retry logic throughout the codebase.&lt;/p&gt;
&lt;h3 id=&quot;2-optimistic-concurrency-control&quot;&gt;2. Optimistic Concurrency Control&lt;/h3&gt;
&lt;p&gt;In multi-user systems, preventing lost updates is critical. EF Core supports optimistic concurrency through concurrency tokens such as SQL Server's &lt;code&gt;rowversion&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;public class Order
{
    public int Id { get; set; }

    [Timestamp]
    public byte[] RowVersion { get; set; }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When another user modifies the same record, EF Core detects the conflict and throws a &lt;code&gt;DbUpdateConcurrencyException&lt;/code&gt;, allowing the application to reload, merge, or notify the user. This ensures data integrity while avoiding expensive database locks.&lt;/p&gt;
&lt;h3 id=&quot;3-performance-optimization-with-compiled-queries&quot;&gt;3. Performance Optimization with Compiled Queries&lt;/h3&gt;
&lt;p&gt;High-throughput APIs and microservices can execute the same queries thousands of times per second. EF Core offers several performance features, including compiled queries, &lt;code&gt;AsNoTracking&lt;/code&gt;, query splitting, and batching.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;private static readonly Func&amp;lt;AppDbContext, int, Task&amp;lt;Order&amp;gt;&amp;gt; GetOrder =
    EF.CompileAsyncQuery((AppDbContext ctx, int id) =&amp;gt;
        ctx.Orders.AsNoTracking()
                  .FirstOrDefault(o =&amp;gt; o.Id == id));
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Compiled queries eliminate repeated LINQ-to-SQL translation overhead, while &lt;code&gt;AsNoTracking&lt;/code&gt; reduces change-tracking costs for read-only operations. These optimizations can significantly improve throughput under load.&lt;/p&gt;
&lt;h3 id=&quot;4-transactions-and-unit-of-work&quot;&gt;4. Transactions and Unit of Work&lt;/h3&gt;
&lt;p&gt;Business operations often involve multiple database changes that must either succeed together or fail together. EF Core supports explicit transactions to guarantee atomicity.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;await using var tx = await context.Database.BeginTransactionAsync();

try
{
    await context.SaveChangesAsync();
    await otherContext.SaveChangesAsync();
    await tx.CommitAsync();
}
catch
{
    await tx.RollbackAsync();
    throw;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This capability is fundamental for maintaining consistency across aggregates, repositories, and critical business workflows.&lt;/p&gt;
&lt;h3 id=&quot;5-global-query-filters&quot;&gt;5. Global Query Filters&lt;/h3&gt;
&lt;p&gt;Global query filters allow rules to be applied automatically to every query for a given entity. They are especially valuable for multi-tenant SaaS applications and soft-delete implementations.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity&amp;lt;Customer&amp;gt;()
        .HasQueryFilter(c =&amp;gt; !c.IsDeleted);

    modelBuilder.Entity&amp;lt;Invoice&amp;gt;()
        .HasQueryFilter(x =&amp;gt; x.TenantId == _currentTenant.Id);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;By centralizing these concerns, developers reduce duplication and eliminate the risk of accidentally exposing data across tenants or returning deleted records.&lt;/p&gt;
&lt;h3 id=&quot;final-thoughts&quot;&gt;Final Thoughts&lt;/h3&gt;
&lt;p&gt;While EF Core is easy to start with, enterprise applications require more than basic CRUD operations. Features such as connection resiliency, concurrency control, performance optimization, transactions, and global query filters help teams build systems that are robust, scalable, and maintainable in production.&lt;/p&gt;
&lt;p&gt;Mastering these five capabilities will provide a strong foundation for building enterprise-grade applications with EF Core.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/19745/5-ef-core-features-every-enterprise-developer-should-know</guid>
<pubDate>Fri, 05 Jun 2026 08:47:24 +0000</pubDate>
</item>
<item>
<title>When IDbContextFactory enters the chat, and what it actually fixes.</title>
<link>https://coderlegion.com/11754/when-idbcontextfactory-enters-the-chat-and-what-it-actually-fixes</link>
<description>&lt;h3 id=&quot;when-idbcontextfactory-enters-the-chat-and-what-it-actually-fixes&quot;&gt;When &lt;code&gt;IDbContextFactory&lt;/code&gt; enters the chat, and what it actually fixes&lt;/h3&gt;
&lt;p&gt;Most EF Core examples use a scoped &lt;code&gt;DbContext&lt;/code&gt;. In a normal Web API request, that’s fine, the request ends, the context is disposed.&lt;/p&gt;
&lt;p&gt;Then you build Blazor Server.&lt;/p&gt;
&lt;p&gt;A Blazor Server circuit can stay alive for a long time, minutes or hours. If your &lt;code&gt;DbContext&lt;/code&gt; is scoped, it can also stay alive that long. That’s when things start to break in annoying ways.&lt;/p&gt;
&lt;h4 id=&quot;what-problems-show-up&quot;&gt;What problems show up&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;1) “A second operation was started on this context…”&lt;/strong&gt;&lt;br&gt;
&lt;code&gt;DbContext&lt;/code&gt; is not thread-safe. In Blazor Server it’s easy to trigger overlapping work (clicks, timers, loading states). If two queries happen at the same time on the same context, you get this error.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;2) Tracking bloat&lt;/strong&gt;&lt;br&gt;
A long-lived context keeps tracking entities. Over time:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;memory grows&lt;/li&gt;
&lt;li&gt;queries can feel slower&lt;/li&gt;
&lt;li&gt;SaveChanges can get heavy&lt;/li&gt;
&lt;li&gt;you hit “already being tracked” errors&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;3) Stale or confusing data&lt;/strong&gt;&lt;br&gt;
Because the context tracks objects, you can end up reusing old instances without realizing it, the UI feels out of sync.&lt;/p&gt;
&lt;h4 id=&quot;what-idbcontextfactory-changes&quot;&gt;What &lt;code&gt;IDbContextFactory&lt;/code&gt; changes&lt;/h4&gt;
&lt;p&gt;It gives you a fresh &lt;code&gt;DbContext&lt;/code&gt; per operation.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;create context&lt;/li&gt;
&lt;li&gt;run the query or command&lt;/li&gt;
&lt;li&gt;dispose the context&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So each click, each load, each action gets its own clean context. No shared state, no concurrency fights, no growing tracker.&lt;/p&gt;
&lt;h4 id=&quot;when-to-use-it&quot;&gt;When to use it&lt;/h4&gt;
&lt;p&gt;Use &lt;code&gt;IDbContextFactory&lt;/code&gt; when you have:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Blazor Server&lt;/li&gt;
&lt;li&gt;background jobs or hosted services&lt;/li&gt;
&lt;li&gt;timers, polling, signalR style event flows&lt;/li&gt;
&lt;li&gt;any parallel DB work&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In simple controller-per-request apps, scoped &lt;code&gt;DbContext&lt;/code&gt; is still perfectly fine.&lt;/p&gt;
&lt;h4 id=&quot;the-takeaway&quot;&gt;The takeaway&lt;/h4&gt;
&lt;p&gt;&lt;code&gt;IDbContextFactory&lt;/code&gt; is not a “nice to have”, it’s the safer default when your app is long-lived and event-driven. It prevents a whole category of EF Core bugs before they happen.&lt;/p&gt;
&lt;p&gt;Source Code :&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/stevsharp/BlazorFactoryDemo&quot;&gt;https://github.com/stevsharp/BlazorFactoryDemo&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;More to read  : &lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://learn.microsoft.com/en-us/dotnet/api/microsoft.entityframeworkcore.idbcontextfactory-1?view=efcore-10.0&quot;&gt;https://learn.microsoft.com/en-us/dotnet/api/microsoft.entityframeworkcore.idbcontextfactory-1?view=efcore-10.0&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://learn.microsoft.com/en-us/ef/core/dbcontext-configuration/&quot;&gt;https://learn.microsoft.com/en-us/ef/core/dbcontext-configuration/&lt;/a&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/11754/when-idbcontextfactory-enters-the-chat-and-what-it-actually-fixes</guid>
<pubDate>Mon, 16 Feb 2026 09:47:23 +0000</pubDate>
</item>
<item>
<title>Reliable Messaging in .NET: Domain Events and the Outbox Pattern with EF Core Interceptors</title>
<link>https://coderlegion.com/6363/reliable-messaging-in-net-domain-events-and-the-outbox-pattern-with-ef-core-interceptors</link>
<description>&lt;h3 id=&quot;introduction&quot;&gt;Introduction&lt;/h3&gt;
&lt;p&gt;Distributed systems are powerful — but they come with one big challenge: &lt;strong&gt;reliability&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;What happens if your application updates a record in the database and then tries to publish a message to a message broker… but crashes in between?&lt;br&gt;
You’ve just lost an event.&lt;/p&gt;
&lt;p&gt;This problem is common in microservices, and it’s why the &lt;strong&gt;Outbox Pattern&lt;/strong&gt; exists — a simple, elegant approach that guarantees message delivery without introducing distributed transactions or external coordinators.&lt;/p&gt;
&lt;p&gt;In this article, we’ll implement a &lt;strong&gt;Domain Event + Outbox Pattern&lt;/strong&gt; in &lt;strong&gt;.NET 9&lt;/strong&gt;, using &lt;strong&gt;Entity Framework Core&lt;/strong&gt;, &lt;strong&gt;SQLite&lt;/strong&gt;, and an &lt;strong&gt;EF Core SaveChangesInterceptor&lt;/strong&gt;.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;-a-quick-look-at-entity-framework-core&quot;&gt;⚙️ A Quick Look at Entity Framework Core&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Entity Framework Core (EF Core)&lt;/strong&gt; is Microsoft’s modern &lt;strong&gt;Object-Relational Mapper (ORM)&lt;/strong&gt; for .NET.&lt;br&gt;
It allows developers to work with a database using &lt;strong&gt;C# classes&lt;/strong&gt; instead of raw SQL, automatically handling relationships, migrations, and change tracking.&lt;/p&gt;
&lt;p&gt;When you call &lt;code&gt;SaveChanges()&lt;/code&gt; or &lt;code&gt;SaveChangesAsync()&lt;/code&gt;, EF Core:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Tracks your entity changes.&lt;/li&gt;
&lt;li&gt;Generates SQL commands.&lt;/li&gt;
&lt;li&gt;Executes them in a transaction.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This makes EF Core the perfect place to integrate the &lt;strong&gt;Outbox Pattern&lt;/strong&gt;, since we can hook into its save pipeline and ensure domain events are written in the same transaction as the data itself.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;what-are-interceptors-&quot;&gt;What Are Interceptors?&lt;/h3&gt;
&lt;p&gt;In EF Core, &lt;strong&gt;interceptors&lt;/strong&gt; are powerful hooks that allow you to intercept and extend the framework’s internal operations — such as saving, querying, or connecting to the database.&lt;/p&gt;
&lt;p&gt;They work like &lt;strong&gt;middleware for EF Core&lt;/strong&gt;:&lt;br&gt;
you can observe, modify, or add logic before and after EF performs certain actions.&lt;/p&gt;
&lt;p&gt;There are several types:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;SaveChangesInterceptor&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;CommandInterceptor&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ConnectionInterceptor&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;TransactionInterceptor&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In this article, we’ll use &lt;strong&gt;SaveChangesInterceptor&lt;/strong&gt; to detect domain events before EF saves, serialize them, and insert them into our Outbox table.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;the-problem&quot;&gt;The Problem&lt;/h3&gt;
&lt;p&gt;In traditional architectures, your service might do something like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;await _db.SaveChangesAsync();
await _bus.PublishAsync(new OrderPlacedEvent(orderId));
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If your app crashes after saving but before publishing, your event never reaches other services — resulting in inconsistent state across systems.&lt;/p&gt;
&lt;p&gt;We need a way to ensure &lt;strong&gt;atomicity&lt;/strong&gt;: either both the data and the event are persisted, or neither are.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;the-outbox-pattern-in-a-nutshell-&quot;&gt;The Outbox Pattern (In a Nutshell)&lt;/h3&gt;
&lt;p&gt;The &lt;strong&gt;Outbox Pattern&lt;/strong&gt; solves this by introducing a dedicated &lt;strong&gt;Outbox table&lt;/strong&gt; inside your database.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;When your aggregate raises a domain event, it’s &lt;strong&gt;captured and saved&lt;/strong&gt; into the Outbox table &lt;strong&gt;within the same transaction&lt;/strong&gt; as your entity.&lt;/li&gt;
&lt;li&gt;A background worker (or a separate service) &lt;strong&gt;polls&lt;/strong&gt; the Outbox table, &lt;strong&gt;publishes events&lt;/strong&gt;, and marks them as processed.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This way, even if your app crashes, the events are safely stored and can be retried later.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;the-architecture&quot;&gt;The Architecture&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;┌──────────────────────────────┐
│         Application          │
│ ──────────────────────────── │
│  Order raises DomainEvent    │
│  → EF Interceptor writes     │
│    Outbox row                │
│  → Transaction commits       │
└──────────────┬───────────────┘
               │
               ▼
        Outbox Dispatcher
       ├─ Polls database
       ├─ Publishes messages
       └─ Marks them processed
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This pattern elegantly blends &lt;strong&gt;Domain-Driven Design&lt;/strong&gt; concepts with &lt;strong&gt;event-driven reliability&lt;/strong&gt;.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;domain-events&quot;&gt;Domain Events&lt;/h3&gt;
&lt;p&gt;Domain events capture something meaningful that happened in your domain model.&lt;br&gt;
For example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;public sealed record OrderPlacedDomainEvent(Guid OrderId, decimal Total)
    : DomainEvent(DateTime.UtcNow);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And your aggregate raises them naturally:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;public sealed class Order : IHasDomainEvents
{
    private readonly List&amp;lt;DomainEvent&amp;gt; _domainEvents = new();
    public Guid Id { get; private set; } = Guid.NewGuid();
    public decimal Total { get; private set; }

    public IReadOnlyCollection&amp;lt;DomainEvent&amp;gt; DomainEvents =&amp;gt; _domainEvents;

    public Order(decimal total)
    {
        Total = total;
        AddEvent(new OrderPlacedDomainEvent(Id, Total));
    }

    private void AddEvent(DomainEvent @event) =&amp;gt; _domainEvents.Add(@event);
    public void ClearDomainEvents() =&amp;gt; _domainEvents.Clear();
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3 id=&quot;-capturing-events-with-an-ef-core-interceptor&quot;&gt;⚙️ Capturing Events with an EF Core Interceptor&lt;/h3&gt;
&lt;p&gt;Instead of overriding &lt;code&gt;SaveChangesAsync()&lt;/code&gt; directly inside &lt;code&gt;AppDbContext&lt;/code&gt;, we can use a &lt;strong&gt;SaveChangesInterceptor&lt;/strong&gt; — a clean, composable way to plug custom logic into EF’s lifecycle.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;
public sealed class OutboxSaveChangesInterceptor(IEventSerializer serializer) : SaveChangesInterceptor
{
    /// &amp;lt;summary&amp;gt;
    /// 
    /// &amp;lt;/summary&amp;gt;
    /// &amp;lt;param name=&quot;eventData&quot;&amp;gt;&amp;lt;/param&amp;gt;
    /// &amp;lt;param name=&quot;result&quot;&amp;gt;&amp;lt;/param&amp;gt;
    /// &amp;lt;param name=&quot;cancellationToken&quot;&amp;gt;&amp;lt;/param&amp;gt;
    /// &amp;lt;returns&amp;gt;&amp;lt;/returns&amp;gt;
    public override async ValueTask&amp;lt;InterceptionResult&amp;lt;int&amp;gt;&amp;gt; SavingChangesAsync(
        DbContextEventData eventData,
        InterceptionResult&amp;lt;int&amp;gt; result,
        CancellationToken cancellationToken = default)
    {
        var context = eventData.Context;
        if (context is null) 
            return await base.SavingChangesAsync(eventData, result, cancellationToken);

        var aggregates = context.ChangeTracker
                    .Entries()
                    .Where(e =&amp;gt; e.Entity is IHasDomainEvents hasEvents &amp;amp;&amp;amp; hasEvents.DomainEvents.Count &amp;gt; 0)
                    .Select(e =&amp;gt; (IHasDomainEvents)e.Entity)
                    .ToList();

        if (aggregates.Count == 0)
            return await base.SavingChangesAsync(eventData, result, cancellationToken);

        var outbox = context.Set&amp;lt;OutboxMessage&amp;gt;();

        foreach (var aggregate in aggregates)
        {
            foreach (var @event in aggregate.DomainEvents)
            {
                outbox.Add(new OutboxMessage
                {
                    OccurredOnUtc = @event.OccurredOnUtc,
                    Type = @event.GetType().AssemblyQualifiedName!,
                    Payload = serializer.Serialize(@event)
                });
            }
        }

        return await base.SavingChangesAsync(eventData, result, cancellationToken);
    }

}

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now every time you call &lt;code&gt;SaveChangesAsync()&lt;/code&gt;, domain events are written to your &lt;strong&gt;OutboxMessages&lt;/strong&gt; table automatically — in the same transaction.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;the-outbox-dispatcher&quot;&gt;The Outbox Dispatcher&lt;/h3&gt;
&lt;p&gt;Once the transaction is committed, a background worker can safely process these messages.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;
public sealed class OutboxDispatcher(IServiceProvider sp, 
    ILogger&amp;lt;OutboxDispatcher&amp;gt; logger) : BackgroundService
{
    private readonly IServiceProvider _sp = sp;
    private readonly ILogger&amp;lt;OutboxDispatcher&amp;gt; _logger = logger;
    private readonly TimeSpan _pollInterval = TimeSpan.FromSeconds(2);
    private const int BatchSize = 50;

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        _logger.LogInformation(&quot; Outbox Dispatcher started...&quot;);

        while (!stoppingToken.IsCancellationRequested)
        {
            try
            {
                using var scope = _sp.CreateScope();
                var db = scope.ServiceProvider.GetRequiredService&amp;lt;AppDbContext&amp;gt;();

                var jsonSerializer = scope.ServiceProvider.GetRequiredService&amp;lt;IEventSerializer&amp;gt;();

                var pending = await db.OutboxMessages
                    .Where(x =&amp;gt; x.ProcessedOnUtc == null)
                    .OrderBy(x =&amp;gt; x.Id)
                    .Take(BatchSize)
                    .ToListAsync(stoppingToken);

                foreach (var msg in pending)
                {
                    try
                    {
                        var evt = jsonSerializer.Deserialize(msg.Payload, msg.Type);
                        await SimulatedBusPublish(evt, stoppingToken);
                        msg.ProcessedOnUtc = DateTime.UtcNow;
                        msg.Error = null;
                    }
                    catch (Exception ex)
                    {
                        msg.Error = ex.Message;
                        _logger.LogError(ex, &quot;❌ Error dispatching OutboxMessage {Id}&quot;, msg.Id);
                    }
                }

                if (pending.Count &amp;gt; 0)
                    await db.SaveChangesAsync(stoppingToken);
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, &quot;Outbox dispatcher loop error&quot;);
            }

            await Task.Delay(_pollInterval, stoppingToken);
        }
    }

    private Task SimulatedBusPublish(DomainEvent evt, CancellationToken ct)
        =&amp;gt; evt switch
        {
            OrderPlacedDomainEvent e =&amp;gt; HandleOrderPlaced(e),
            _ =&amp;gt; Task.CompletedTask
        };

    private Task HandleOrderPlaced(OrderPlacedDomainEvent e)
    {
        Console.WriteLine($&quot;[BUS] ✅ OrderPlaced published =&amp;gt; OrderId={e.OrderId}, Total={e.Total}&quot;);

        return Task.CompletedTask;
    }
}

&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3 id=&quot;why-use-an-interceptor-&quot;&gt;Why Use an Interceptor?&lt;/h3&gt;
&lt;p&gt;Using an &lt;strong&gt;interceptor&lt;/strong&gt; instead of overriding &lt;code&gt;SaveChangesAsync()&lt;/code&gt; has several advantages:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Approach                      &lt;/th&gt;&lt;th&gt;Pros                          &lt;/th&gt;&lt;th&gt;Cons&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Override SaveChangesAsync&lt;/strong&gt; &lt;/td&gt;&lt;td&gt;Simple to start               &lt;/td&gt;&lt;td&gt;Harder to reuse across contexts&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Use Interceptor&lt;/strong&gt;           &lt;/td&gt;&lt;td&gt;Decoupled, reusable, testable &lt;/td&gt;&lt;td&gt;Slightly more setup&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Interceptors follow the &lt;strong&gt;Open/Closed Principle&lt;/strong&gt; — your DbContext remains focused on persistence, while cross-cutting logic (like event logging, auditing, or outbox handling) is easily attachable.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;benefits-of-the-outbox-pattern&quot;&gt;Benefits of the Outbox Pattern&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;✅ &lt;strong&gt;Transactional Safety&lt;/strong&gt; — data + events saved atomically&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;Guaranteed Delivery&lt;/strong&gt; — retries handled by dispatcher&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;DDD Alignment&lt;/strong&gt; — aggregates raise domain events naturally&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;Resilience&lt;/strong&gt; — works even if message broker is offline&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;Extensibility&lt;/strong&gt; — can integrate with RabbitMQ, Kafka, or Azure Service Bus later&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id=&quot;references&quot;&gt;References&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://learn.microsoft.com/en-us/ef/core/logging-events-diagnostics/interceptors&quot;&gt;Microsoft Docs – EF Core Interceptors&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://martinfowler.com/articles/patterns-of-distributed-systems/transactional-outbox.html&quot;&gt;Martin Fowler – Transactional Outbox Pattern&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://udidahan.com/2008/05/14/when-to-use-sagas/&quot;&gt;Udi Dahan – Reliable Messaging without Distributed Transactions&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://learn.microsoft.com/en-us/dotnet/core/extensions/workers&quot;&gt;Microsoft Docs – BackgroundService in .NET&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://www.jimmybogard.com/domain-events-outbox-pattern/&quot;&gt;Jimmy Bogard – Domain Events and the Outbox Pattern in .NET&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/stevsharp/OutboxPattern&quot;&gt;Source Code &lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h3 id=&quot;-closing-thoughts&quot;&gt;✨ Closing Thoughts&lt;/h3&gt;
&lt;p&gt;The &lt;strong&gt;Domain Events + Outbox Pattern&lt;/strong&gt; is one of the most practical ways to achieve &lt;strong&gt;reliable, eventually consistent event-driven systems&lt;/strong&gt; in .NET.&lt;/p&gt;
&lt;p&gt;By combining &lt;strong&gt;Entity Framework Core&lt;/strong&gt;, &lt;strong&gt;SaveChangesInterceptor&lt;/strong&gt;, and a simple &lt;strong&gt;background dispatcher&lt;/strong&gt;, you can ensure every event is persisted and delivered — even in the face of failure.&lt;/p&gt;
&lt;p&gt;Small pattern. Big reliability. &lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&quot;author&quot;&gt;Author&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;Spyros Ponaris&lt;/strong&gt;&lt;br&gt;
 &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.linkedin.com/in/spyros-ponaris-913a6937/&quot;&gt;LinkedIn&lt;/a&gt;&lt;br&gt;
 Senior Software Engineer &amp;amp; .NET Enthusiast&lt;/p&gt;
</description>
<category>C#</category>
<guid isPermaLink="true">https://coderlegion.com/6363/reliable-messaging-in-net-domain-events-and-the-outbox-pattern-with-ef-core-interceptors</guid>
<pubDate>Mon, 13 Oct 2025 11:27:53 +0000</pubDate>
</item>
<item>
<title>Supercharging EF Core Specifications with EF.CompileQuery</title>
<link>https://coderlegion.com/4443/supercharging-ef-core-specifications-with-ef-compilequery</link>
<description>&lt;ol&gt;
&lt;li&gt;Introduction&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;When you’re using EF Core in production, query compilation can quietly become a performance bottleneck — especially if the same query is executed thousands of times.&lt;/p&gt;
&lt;p&gt;Normally, EF Core parses your LINQ, generates SQL, and prepares the execution plan every time.&lt;br&gt;
With EF.CompileQuery, EF Core does that work once, caching the query plan for all future executions.&lt;/p&gt;
&lt;p&gt;The catch?&lt;/p&gt;
&lt;p&gt;If you’re using the Specification pattern for clean, reusable queries, your repository method probably applies a spec dynamically to DbSet — which makes EF.CompileQuery less obvious to use.&lt;/p&gt;
&lt;p&gt;In this article, we’ll solve that and show you how to bake compiled queries into your specification-driven repository.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Quick Recap: Specification Pattern&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The Specification pattern encapsulates query logic into reusable, composable units.&lt;/p&gt;
&lt;p&gt;Example spec for active customers:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public class ActiveCustomersSpec : Specification&amp;lt;Customer&amp;gt;
{
    public ActiveCustomersSpec()
    {
        Query.Where(c =&amp;gt; c.IsActive)
             .OrderBy(c =&amp;gt; c.Name)
             .Include(c =&amp;gt; c.Orders);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Your repository then applies the spec:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public async Task&amp;lt;IReadOnlyList&amp;lt;T&amp;gt;&amp;gt; ListAsync(ISpecification&amp;lt;T&amp;gt; spec)
{
    var evaluator = new SpecificationEvaluator();
    var query = evaluator.GetQuery(_db.Set&amp;lt;T&amp;gt;().AsQueryable(), spec);
    return await query.ToListAsync();
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;The Problem&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;EF.CompileQuery needs a strongly-typed lambda starting from your DbContext:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;private static readonly Func&amp;lt;AppDbContext, bool, IEnumerable&amp;lt;Customer&amp;gt;&amp;gt; _activeCustomers =
    EF.CompileQuery((AppDbContext db, bool isActive) =&amp;gt;
        db.Customers.Where(c =&amp;gt; c.IsActive == isActive));
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;But with the Specification pattern, you usually pass in a spec, not a lambda on DbContext.&lt;br&gt;
You don’t want every caller to remember how to invoke the compiled query.&lt;br&gt;
We need a way to integrate compiled queries into the repository itself.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;The Solution: Encapsulate Compiled Queries in the Repository&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;4.1 Create the compiled query&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public static class CompiledQueries
{
    public static readonly Func&amp;lt;AppDbContext, bool, IEnumerable&amp;lt;Customer&amp;gt;&amp;gt; ActiveCustomers =
        EF.CompileQuery((AppDbContext db, bool isActive) =&amp;gt;
            db.Customers
              .Where(c =&amp;gt; c.IsActive == isActive)
              .OrderBy(c =&amp;gt; c.Name)
              .Include(c =&amp;gt; c.Orders));
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;4.2 Update the repository&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public class EfRepository&amp;lt;T&amp;gt; where T : class
{
    private readonly AppDbContext _db;
    public EfRepository(AppDbContext db) =&amp;gt; _db = db;

    public async Task&amp;lt;IReadOnlyList&amp;lt;T&amp;gt;&amp;gt; ListAsync(ISpecification&amp;lt;T&amp;gt; spec)
    {
        // Detect known spec → use compiled query
        if (typeof(T) == typeof(Customer) &amp;amp;&amp;amp; spec is ActiveCustomersSpec)
        {
            var result = CompiledQueries.ActiveCustomers(_db, true).ToList();
            return (IReadOnlyList&amp;lt;T&amp;gt;)result;
        }

        // Fallback: normal spec evaluation
        var evaluator = new SpecificationEvaluator();
        var query = evaluator.GetQuery(_db.Set&amp;lt;T&amp;gt;().AsQueryable(), spec);
        return await query.ToListAsync();
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;4.3 Usage stays clean&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;var repo = new EfRepository&amp;lt;Customer&amp;gt;(db);
var customers = await repo.ListAsync(new ActiveCustomersSpec());
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The caller doesn’t know (or care) if the query was compiled&lt;br&gt;
but hot-path specs now run ~3x faster.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://coderlegion.com/?qa=blob&amp;amp;qa_blobid=16149713430744441257&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Ready-to-Use Libraries&lt;br&gt;
Instead of writing your own Specification pattern, you can use:&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/ardalis/Specification&quot;&gt;Ardalis.Specification&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Well-maintained, feature-rich.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Supports includes, projections, ordering, pagination.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/miholler/NSpecifications&quot;&gt;NSpecifications&lt;/a&gt; &lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;DDD-focused, composable specifications.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Great for domain-driven designs.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;  Both integrate nicely with EF Core and can work with this&lt;br&gt;
   compiled-query approach.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Best Practices&lt;br&gt;
Use compiled queries for hot paths — they have a cost to set up but shine when reused often.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Avoid over-compiling — not every query benefits from precompilation.&lt;/p&gt;
&lt;p&gt;Keep specs small and focused — huge includes and joins can still dominate runtime.&lt;/p&gt;
&lt;p&gt;Combine with async wisely — EF.CompileQuery is sync; use EF.CompileAsyncQuery for async enumeration.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Conclusion&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;You don’t have to choose between clean architecture (Specification pattern) and speed.&lt;br&gt;
By encapsulating EF.CompileQuery inside your repository for frequently-used specs,&lt;br&gt;
you can keep your calling code clean while still getting huge performance wins.&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/stevsharp/EfCompileSpecDemo&quot;&gt;Source Code&lt;/a&gt; : &lt;br&gt;
&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/stevsharp/EfCompileSpecDemo&quot;&gt;https://github.com/stevsharp/EfCompileSpecDemo&lt;/a&gt;&lt;/p&gt;
</description>
<category>C#</category>
<guid isPermaLink="true">https://coderlegion.com/4443/supercharging-ef-core-specifications-with-ef-compilequery</guid>
<pubDate>Tue, 05 Aug 2025 07:56:23 +0000</pubDate>
</item>
<item>
<title>⚔️ The Mighty MERGE: Using SQL Merge Statements Safely with EF Core</title>
<link>https://coderlegion.com/4268/the-mighty-merge-using-sql-merge-statements-safely-with-ef-core</link>
<description>&lt;p&gt;When you’re working with bulk data operations, upserts, or syncing external sources, MERGE is one of the most powerful tools SQL Server offers. But what happens when you need to use it alongside Entity Framework Core?&lt;/p&gt;
&lt;p&gt;In this article, we’ll cover:&lt;/p&gt;
&lt;p&gt;✅ What MERGE is and why it matters&lt;/p&gt;
&lt;p&gt; How to use it with EF Core (with and without transactions)&lt;/p&gt;
&lt;p&gt;⚠️ Common pitfalls and best practices&lt;/p&gt;
&lt;p&gt;️ Alternatives like BulkExtensions and PostgreSQL upserts&lt;/p&gt;
&lt;p&gt; When to use MERGE — and when not to&lt;/p&gt;
&lt;p&gt; What Is SQL MERGE?&lt;/p&gt;
&lt;p&gt;MERGE is a T-SQL command that allows you to INSERT, UPDATE, or DELETE in a single statement based on conditions between a source and a target.&lt;/p&gt;
&lt;p&gt;Simple Example:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;MERGE INTO Products AS Target
USING (VALUES (1, 'Apple'), (2, 'Banana')) AS Source(Id, Name)
    ON Target.Id = Source.Id
WHEN MATCHED THEN
    UPDATE SET Name = Source.Name
WHEN NOT MATCHED THEN
    INSERT (Id, Name) VALUES (Source.Id, Source.Name);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;✅ The power of MERGE lies in combining multiple operations in one atomic transaction — perfect for syncing external sources, staging tables, or upserting data without multiple round-trips.&lt;/p&gt;
&lt;p&gt; Using MERGE with EF Core&lt;br&gt;
EF Core does not expose MERGE as part of its LINQ API. But you can still run raw SQL safely using:&lt;/p&gt;
&lt;p&gt; Option 1: ExecuteSqlRaw&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;await dbContext.Database.ExecuteSqlRawAsync(@&quot;
MERGE INTO Products AS Target
USING (VALUES (1, 'Apple'), (2, 'Banana')) AS Source(Id, Name)
ON Target.Id = Source.Id
WHEN MATCHED THEN
    UPDATE SET Name = Source.Name
WHEN NOT MATCHED THEN
    INSERT (Id, Name) VALUES (Source.Id, Source.Name);&quot;);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt; Option 2: Call a Stored Procedure&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;CREATE PROCEDURE MergeProducts AS
BEGIN
    MERGE INTO Products ...
END

await dbContext.Database.ExecuteSqlRawAsync(&quot;EXEC MergeProducts&quot;);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt; Option 3: Use EF Core Bulk Libraries&lt;/p&gt;
&lt;p&gt;Libraries like &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/borisdj/EFCore.BulkExtensions&quot;&gt;EFCore.BulkExtensions&lt;/a&gt; simulate efficient MERGE behavior:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;await dbContext.BulkInsertOrUpdateAsync(products);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt; Can I Use MERGE with EF Core Transactions?&lt;/p&gt;
&lt;p&gt;Yes — and you should if you’re performing other EF operations in the same workflow. EF Core supports shared transactions across both raw SQL and EF operations.&lt;/p&gt;
&lt;p&gt;✅ Full Example:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;using var transaction = await dbContext.Database.BeginTransactionAsync();

try
{
    // 1. Execute MERGE
    await dbContext.Database.ExecuteSqlRawAsync(@&quot;
        MERGE INTO Products AS Target
        USING (VALUES (1, 'Apple'), (2, 'Banana')) AS Source(Id, Name)
        ON Target.Id = Source.Id
        WHEN MATCHED THEN
            UPDATE SET Name = Source.Name
        WHEN NOT MATCHED THEN
            INSERT (Id, Name) VALUES (Source.Id, Source.Name);&quot;);

    dbContext.SyncLogs.Add(new SyncLog { Table = &quot;Products&quot;, Timestamp = DateTime.UtcNow });
    await dbContext.SaveChangesAsync();

    await transaction.CommitAsync();
}
    catch
{
    await transaction.RollbackAsync();
    throw;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;✅ This ensures the MERGE and the SyncLogs insert are part of the same database transaction, protecting you from partial updates.&lt;br&gt;
&lt;img src=&quot;https://coderlegion.com/?qa=blob&amp;amp;qa_blobid=5036699520188165910&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;⚠️ Warnings About SQL Server MERGE&lt;/p&gt;
&lt;p&gt;Microsoft has acknowledged &lt;a rel=&quot;nofollow&quot; href=&quot;https://learn.microsoft.com/en-us/sql/t-sql/statements/merge-transact-sql?view=sql-server-ver16#remarks&quot;&gt;multiple bug&lt;/a&gt;s with MERGE in SQL Server — including:&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://learn.microsoft.com/en-us/sql/t-sql/statements/merge-transact-sql?view=sql-server-ver16#remarks&quot;&gt;https://learn.microsoft.com/en-us/sql/t-sql/statements/merge-transact-sql?view=sql-server-ver16#remarks&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Unexpected behavior with triggers&lt;/p&gt;
&lt;p&gt;Incorrect OUTPUT clauses&lt;/p&gt;
&lt;p&gt;Problems with concurrent updates&lt;/p&gt;
&lt;p&gt;✅ Safer Patterns&lt;/p&gt;
&lt;p&gt;Use UPDATE ... and INSERT WHERE NOT EXISTS if possible&lt;/p&gt;
&lt;p&gt;Avoid complex MERGE statements with multiple joins&lt;/p&gt;
&lt;p&gt;Always test MERGE inside a transaction in production-like data scenarios&lt;/p&gt;
&lt;p&gt; When Should I Use MERGE?&lt;/p&gt;
&lt;p&gt;✅ Use MERGE if:&lt;/p&gt;
&lt;p&gt;You need to sync two tables (e.g., staging import)&lt;/p&gt;
&lt;p&gt;You want to upsert large volumes in one atomic step&lt;/p&gt;
&lt;p&gt;You’re doing data warehousing or ETL pipelines&lt;/p&gt;
&lt;p&gt;❌ Avoid MERGE if:&lt;/p&gt;
&lt;p&gt;You're only updating/inserting a few rows&lt;/p&gt;
&lt;p&gt;You rely on entity validation logic in your app&lt;/p&gt;
&lt;p&gt;You need maximum concurrency safety without side effects&lt;/p&gt;
&lt;p&gt;✨ Bonus: Reusable MERGE Helper&lt;br&gt;
You can build a helper that accepts any raw SQL and executes it within a transaction:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public static async Task ExecuteMergeAsync(this DbContext db, string sql)
{
    using var tx = await db.Database.BeginTransactionAsync();
    try
    {
        await db.Database.ExecuteSqlRawAsync(sql);
        await tx.CommitAsync();
    }
    catch
    {
        await tx.RollbackAsync();
        throw;
    }

}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt; Conclusion&lt;/p&gt;
&lt;p&gt;SQL MERGE is a mighty tool for any developer working with bulk updates, external data, or data sync pipelines. EF Core doesn’t have built-in support  but raw SQL, stored procs, and bulk extensions let you use it safely and effectively.&lt;/p&gt;
&lt;p&gt;Wrap it in transactions, know when it shines, and watch out for its quirks  and you’ll get the best of both SQL Server and EF Core worlds.&lt;/p&gt;
</description>
<category>C#</category>
<guid isPermaLink="true">https://coderlegion.com/4268/the-mighty-merge-using-sql-merge-statements-safely-with-ef-core</guid>
<pubDate>Fri, 25 Jul 2025 12:16:46 +0000</pubDate>
</item>
<item>
<title>Optimistic vs. Pessimistic Concurrency in EF Core (with Table Hints)</title>
<link>https://coderlegion.com/1720/optimistic-vs-pessimistic-concurrency-in-ef-core-with-table-hints</link>
<description>&lt;p&gt;Many experienced developers are familiar with database transactions but often overlook the key differences between optimistic and pessimistic concurrency control in Entity Framework Core (EF Core). &lt;/p&gt;
&lt;p&gt;Understanding both approaches and how EF Core handles concurrency tokens is essential for building scalable and safe data operations, especially in systems with high transaction rates.&lt;/p&gt;
&lt;p&gt;Concurrency control determines how multiple users or processes interact with the same data simultaneously. While optimistic concurrency is the default in EF Core, pessimistic concurrency is often misunderstood, misused, or completely ignored  even by seniors engineers.&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://learn.microsoft.com/en-us/ef/core/saving/concurrency?tabs=data-annotations&quot;&gt; Related Read: Understanding and Using Table Hints in SQL Server&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;You can find the full code example at: &lt;br&gt;
&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/stevsharp/EfCoreExamples/tree/Efcore-Concurrency&quot;&gt;https://github.com/stevsharp/EfCoreExamples/tree/Efcore-Concurrency&lt;/a&gt;&lt;/p&gt;
&lt;p&gt; What Is a Concurrency Token?&lt;/p&gt;
&lt;p&gt;A concurrency token is a property that EF Core watches to detect whether a row has been modified since it was loaded. It's typically used in optimistic concurrency scenarios.&lt;/p&gt;
&lt;p&gt;EF Core uses this token during &lt;strong&gt;SaveChangesAsync()&lt;/strong&gt; to generate a WHERE clause that checks if the value is unchanged , if it has changed, a &lt;strong&gt;DbUpdateConcurrencyException&lt;/strong&gt; is thrown.&lt;/p&gt;
&lt;p&gt; How to Mark a Property as a Concurrency Token&lt;/p&gt;
&lt;p&gt;✅ Using [ConcurrencyCheck] Attribute:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public class Product
{
    public int Id { get; set; }

    [ConcurrencyCheck]
    public int Stock { get; set; }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;✅ Using [Timestamp] (for SQL Server rowversion column):&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[Timestamp]
public byte[] RowVersion { get; set; }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;✅ Fluent Configuration:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;modelBuilder.Entity&amp;lt;Product&amp;gt;()
    .Property(p =&amp;gt; p.Stock)
    .IsConcurrencyToken();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can use any column as a concurrency token — even logical fields like LastModified, but &lt;strong&gt; (aka &lt;/strong&gt;) is the most reliable for SQL Server.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Optimistic Concurrency Control&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Optimistic concurrency assumes conflicts are rare, so it doesn’t lock any data during updates. Instead, it tracks changes using concurrency tokens and throws an exception if a conflict is detected during SaveChangesAsync().&lt;/p&gt;
&lt;p&gt;✅ When to Use&lt;/p&gt;
&lt;p&gt;Read-heavy apps with occasional writes (e.g., blog, e-commerce frontend)&lt;/p&gt;
&lt;p&gt;Systems where retries are acceptable&lt;/p&gt;
&lt;p&gt; How to Implement in EF Core with a Concurrency Token&lt;/p&gt;
&lt;p&gt;Entity:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public class Product
{
    public int Id { get; set; }
    public int Stock { get; set; }

    [Timestamp] // This is the concurrency token
    public byte[] RowVersion { get; set; }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Save and Handle Concurrency:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;var product = await context.Products.FindAsync(1);&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;if (product.Stock &amp;gt; 0)
{
    product.Stock -= 1;

    try
    {
        await context.SaveChangesAsync();
    }
    catch (DbUpdateConcurrencyException ex)
    {
        var databaseValues = await ex.Entries.Single().GetDatabaseValuesAsync();
        ex.Entries.Single().OriginalValues.SetValues(databaseValues);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;Pessimistic Concurrency Control&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Pessimistic concurrency prevents conflicts altogether by locking the rows being read or modified. This ensures no one else can change the data until the transaction is complete.&lt;/p&gt;
&lt;p&gt;❗ Important: EF Core does not support table hints like `` in LINQ. These must be applied using raw SQL.&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://learn.microsoft.com/en-us/ef/core/saving/concurrency?tabs=data-annotations&quot;&gt; Related Read: Understanding and Using Table Hints in SQL Server&lt;/a&gt;&lt;/p&gt;
&lt;p&gt; Example: Locking Bank Account Row&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;using (var context = new AppDbContext())
{
    using var transaction = await context.Database.BeginTransactionAsync();

    var account = await context.Accounts
        .FromSqlRaw(&quot;SELECT * FROM Accounts WITH (UPDLOCK, ROWLOCK) WHERE Id = {0}&quot;, 1)
        .AsTracking() // Ensures the entity is tracked for updates
        .FirstOrDefaultAsync();

    if (account != null)
    {
        account.Balance -= 100;

        await context.SaveChangesAsync();
    }

    await transaction.CommitAsync();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;}&lt;/p&gt;
&lt;p&gt; &lt;strong&gt;Why SaveChangesAsync() Alone Is Not Always Enough&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;When using FromSqlRaw(), EF Core may not track the entity correctly for updates unless explicitly instructed.&lt;/p&gt;
&lt;p&gt;Using .AsTracking() ensures EF will detect changes.&lt;/p&gt;
&lt;p&gt;Without it, you would need to manually mark the entity as modified:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;context.Entry(account).State = EntityState.Modified;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt; &lt;img src=&quot;https://coderlegion.com/?qa=blob&amp;amp;qa_blobid=8344402934722218379&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;✅ Final Thoughts&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Concurrency tokens&lt;/strong&gt; (like RowVersion) play a central role in optimistic concurrency, enabling EF Core to detect conflicts without using locks.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Optimistic concurrency&lt;/strong&gt; is ideal for most applications where data collisions are rare and performance is key.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Pessimistic concurrency&lt;/strong&gt; is essential in financial or high-risk systems, where consistency outweighs performance.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;EF Core does not support table hints like&lt;/strong&gt; &lt;code&gt;` in LINQ, but you can **leverage them via **&lt;/code&gt;`, with .AsTracking() to ensure updates are recognized.&lt;/p&gt;
&lt;p&gt;Many developers — even experienced ones — underestimate how concurrency should be managed in EF Core. The wrong approach can result in subtle bugs, lost data, or race conditions in production.&lt;/p&gt;
&lt;p&gt;  References &amp;amp; Further Reading&lt;/p&gt;
&lt;p&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://learn.microsoft.com/en-us/ef/core/saving/concurrency?tabs=data-annotations&quot;&gt;Microsoft Docs – Handling Concurrency Conflicts in EF Core&lt;/a&gt;&lt;/p&gt;
&lt;p&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://learn.microsoft.com/en-us/ef/core/saving/concurrency?tabs=data-annotations&quot;&gt;Microsoft Docs – Concurrency Tokens&lt;/a&gt;&lt;/p&gt;
&lt;p&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://dev.to/stevsharp/understanding-and-using-table-hints-in-sql-server-4iia&quot;&gt;Dev.to – Understanding and Using Table Hints in SQL Server&lt;/a&gt;&lt;/p&gt;
&lt;p&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://learn.microsoft.com/en-us/ef/core/querying/sql-queries?tabs=sqlserver&quot;&gt;Microsoft Docs – Raw SQL Queries in EF Core&lt;/a&gt;&lt;/p&gt;
&lt;p&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://learn.microsoft.com/en-us/sql/t-sql/queries/hints-transact-sql-table?view=sql-server-ver16&quot;&gt;SQL Server – Table Hints in Transact-SQL&lt;/a&gt;&lt;/p&gt;
</description>
<category>C#</category>
<guid isPermaLink="true">https://coderlegion.com/1720/optimistic-vs-pessimistic-concurrency-in-ef-core-with-table-hints</guid>
<pubDate>Mon, 31 Mar 2025 10:01:25 +0000</pubDate>
</item>
<item>
<title>EF Core Bulk Updates and Deletes: Performance vs Change Tracking</title>
<link>https://coderlegion.com/1640/ef-core-bulk-updates-and-deletes-performance-vs-change-tracking</link>
<description>&lt;p&gt;Entity Framework Core 7 introduced two powerful methods for bulk operations: &lt;/p&gt;
&lt;p&gt;&lt;strong&gt;ExecuteUpdateAsync&lt;/strong&gt; and &lt;strong&gt;ExecuteDeleteAsync&lt;/strong&gt;. &lt;/p&gt;
&lt;p&gt;These methods are designed for high-performance scenarios where you need to update or delete multiple records efficiently without loading them into memory. They work great, but there's one caveat: EF Core's Change Tracker is not aware of these changes.&lt;/p&gt;
&lt;p&gt;In this article, we explore how these methods work, why they bypass the &lt;strong&gt;Change Tracker&lt;/strong&gt;, and most importantly—how you can audit or track changes manually when using them.&lt;/p&gt;
&lt;p&gt;If you're not familiar with EF Core's Change Tracker, check out this &lt;a rel=&quot;nofollow&quot; href=&quot;https://learn.microsoft.com/en-us/ef/core/change-tracking/&quot;&gt;introductory guide on Change Tracking&lt;/a&gt; to understand how EF Core monitors entity states.&lt;/p&gt;
&lt;p&gt;Why Use ExecuteUpdateAsync and ExecuteDeleteAsync?&lt;/p&gt;
&lt;p&gt;These methods:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Generate direct SQL UPDATE and DELETE statements.&lt;/li&gt;
&lt;li&gt;Do not load entities into memory, improving performance.&lt;/li&gt;
&lt;li&gt;Bypass change tracking, which avoids unnecessary overhead.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Example Usage:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// Bulk update
await context.Users
    .Where(u =&amp;gt; u.LastLogin &amp;lt; DateTime.UtcNow.AddYears(-1))
    .ExecuteUpdateAsync(setters =&amp;gt; setters.SetProperty(u =&amp;gt; u.IsInactive, true));

// Bulk delete
await context.Users
    .Where(u =&amp;gt; u.IsInactive)
    .ExecuteDeleteAsync();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;The Problem: No Change Tracking&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Because EF Core does not load entities into memory during these operations, the Change Tracker never sees them. This means:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;No automatic change detection.&lt;/li&gt;
&lt;li&gt;No SaveChangesAsync() involvement.&lt;/li&gt;
&lt;li&gt;No triggers for audit or domain events.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;So, how do you track what was changed or deleted?&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;1.Auditing Options for Bulk Operations&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Manual Pre-Query Before Execution&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Fetch affected entities before performing the batch operation.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;var affectedUsers = await context.Users
    .Where(u =&amp;gt; u.LastLogin &amp;lt; DateTime.UtcNow.AddYears(-1))
    .Select(u =&amp;gt; new { u.Id, u.Name, OldValue = u.IsInactive })
    .ToListAsync();

await context.Users
    .Where(u =&amp;gt; u.LastLogin &amp;lt; DateTime.UtcNow.AddYears(-1))
    .ExecuteUpdateAsync(setters =&amp;gt; setters.SetProperty(u =&amp;gt; u.IsInactive, true));

// Log changes manually
foreach (var user in affectedUsers)
{
    Console.WriteLine($&quot;User {user.Name} updated: IsInactive {user.OldValue} → true&quot;);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Pros:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Precise control over what you log.&lt;/p&gt;
&lt;p&gt;Access to old values.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Cons:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Requires an extra query.&lt;/p&gt;
&lt;p&gt;May impact performance for large datasets.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Audit Table Logging&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Save the affected data into an audit table before executing the bulk operation.&lt;/p&gt;
&lt;p&gt;Example:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;await context.AuditLogs.AddRangeAsync(affectedUsers.Select(user =&amp;gt; new AuditLog
{
    EntityName = &quot;User&quot;,
    EntityId = user.Id,
    ChangeType = &quot;Updated&quot;,
    ChangedAt = DateTime.UtcNow,
    Description = $&quot;User {user.Name} was updated: IsInactive {user.OldValue} → true&quot;
}));

await context.SaveChangesAsync();
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;Database Triggers&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Define SQL triggers to capture changes directly in the database.&lt;/p&gt;
&lt;p&gt;Example :&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;CREATE TRIGGER trg_User_Delete
ON Users
AFTER DELETE
AS
BEGIN
    INSERT INTO AuditLogs (EntityName, EntityId, ChangeType, ChangedAt, Description)
    SELECT 'User', Id, 'Deleted', GETUTCDATE(), CONCAT('User ', Name, ' was deleted') FROM deleted;
END;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Pros:&lt;/p&gt;
&lt;p&gt;Fully automated.&lt;/p&gt;
&lt;p&gt;Ensures auditing even if EF Core is bypassed.&lt;/p&gt;
&lt;p&gt;Cons:&lt;/p&gt;
&lt;p&gt;Less flexible.&lt;/p&gt;
&lt;p&gt;Harder to manage and version control.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;EF Core Interceptors (Advanced)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Capture executed SQL commands using DbCommandInterceptor.&lt;/p&gt;
&lt;p&gt;Limitation:&lt;/p&gt;
&lt;p&gt;You can log the SQL query but not the previous values of affected entities.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public class AuditInterceptor : DbCommandInterceptor
{
    public override InterceptionResult&amp;lt;DbDataReader&amp;gt; ReaderExecuting(
        DbCommand command, CommandEventData eventData, InterceptionResult&amp;lt;DbDataReader&amp;gt; result)
    {
        Console.WriteLine($&quot;SQL Executed: {command.CommandText}&quot;);
        return base.ReaderExecuting(command, eventData, result);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Registering the Interceptor : &lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public class ApplicationDbContext : DbContext
{
    public DbSet&amp;lt;User&amp;gt; Users { get; set; }
    public DbSet&amp;lt;AuditLog&amp;gt; AuditLogs { get; set; }

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        optionsBuilder.UseSqlServer(&quot;YourConnectionStringHere&quot;);
        optionsBuilder.AddInterceptors(new AuditInterceptor());
    }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Conclusion&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;EF Core's ExecuteUpdateAsync and ExecuteDeleteAsync offer incredible performance for bulk data operations but come at the cost of losing automatic change tracking.&lt;/p&gt;
&lt;p&gt;If you need to audit or track changes, the best approach is to manually query the affected data before execution or use a dedicated audit system such as audit tables or SQL triggers.&lt;/p&gt;
&lt;p&gt;Choose the method that balances performance and auditing needs for your application.&lt;/p&gt;
&lt;p&gt;What do you think? Do you prefer performance with manual tracking, or do you rely on EF Core's change tracking? Let us know your approach!&lt;/p&gt;
&lt;p&gt;You can find the full code example at: &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/stevsharp/EfCoreExamples/tree/BulkUpdateAndDeletes&quot;&gt;https://github.com/stevsharp/EfCoreExamples/tree/BulkUpdateAndDeletes&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;References&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://learn.microsoft.com/en-us/ef/core/what-is-new/ef-core-7.0/whatsnew#bulk-updates-and-deletes&quot;&gt;Microsoft Docs - EF Core 7: ExecuteUpdate and ExecuteDelete&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://learn.microsoft.com/en-us/ef/core/change-tracking/&quot;&gt;Microsoft Docs - Change Tracking in EF Core&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://learn.microsoft.com/en-us/ef/core/logging-events-diagnostics/interceptors&quot;&gt;Microsoft Docs - Interceptors in EF Core&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/dotnet/efcore&quot;&gt;Community Discussion - EF Core GitHub Issue on Change Tracking and Bulk Operations&lt;/a&gt;&lt;/p&gt;
</description>
<category>C#</category>
<guid isPermaLink="true">https://coderlegion.com/1640/ef-core-bulk-updates-and-deletes-performance-vs-change-tracking</guid>
<pubDate>Mon, 24 Mar 2025 20:25:26 +0000</pubDate>
</item>
<item>
<title>EF Core Interceptors: Supercharge Your Data Access with the Decorator Pattern</title>
<link>https://coderlegion.com/1602/ef-core-interceptors-supercharge-your-data-access-with-the-decorator-pattern</link>
<description>&lt;p&gt;Learn how to hook into EF Core operations using interceptors, leverage the &lt;strong&gt;Decorator Pattern&lt;/strong&gt; for clean cross-cutting concerns, and see real-world examples like SQL logging and auditing.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Table of Contents&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;A Brief History of EF Core Interceptors&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;What Are EF Core Interceptors?&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;EF Core Interceptors and the Decorator Pattern&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Top Use Cases for EF Core Interceptors&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Example 1: Logging SQL Queries&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Example 2: Auditing SaveChanges&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Final Thoughts&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;a-brief-history-of-ef-core-interceptors&quot;&gt;A Brief History of EF Core Interceptors&lt;/h3&gt;
&lt;p&gt;EF Core interceptors were first introduced in EF Core 3.0 (released in September 2019). This version marked a significant step forward in EF Core's extensibility, allowing developers to intercept low-level database operations like commands, connections, and transactions.&lt;/p&gt;
&lt;p&gt;Since then, Microsoft has expanded interceptor capabilities in newer versions:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;EF Core 5.0&lt;/strong&gt;: Introduced SaveChanges interceptors for tracking changes at the context level.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;EF Core 6.0+&lt;/strong&gt;: Continued performance improvements and better support for asynchronous interception.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What Are EF Core Interceptors?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;In Entity Framework Core (EF Core), interceptors allow developers to hook into the execution pipeline of EF Core's operations. Think of them as middleware for your database interactions. You can intercept events such as:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;p&gt;SQL command execution&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Connection opening/closing&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Transactions&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;SaveChanges lifecycle&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This lets you inject custom logic like &lt;strong&gt;logging&lt;/strong&gt;, &lt;strong&gt;auditing&lt;/strong&gt;, &lt;strong&gt;validation&lt;/strong&gt;, or performance metrics without modifying your application’s core logic.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;EF Core Interceptors and the Decorator Pattern&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;EF Core interceptors are a real-world application of the Decorator Pattern, a classic design pattern where functionality is wrapped around existing behavior to extend or alter it.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The core EF operation is the base object.&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;Your interceptor is the decorator.&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;You add behavior before or after EF Core’s operation — without altering EF’s core.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;This approach promotes clean code, separation of concerns, and makes your custom behavior reusable and testable.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Top Use Cases for EF Core Interceptors&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;– SQL Query Logging &lt;/p&gt;
&lt;p&gt;– Track database queries for performance or debugging. &lt;/p&gt;
&lt;p&gt;– Auditing – Automatically log user actions and timestamps. &lt;/p&gt;
&lt;p&gt;– Soft Deletes – Intercept delete commands and convert them to updates. &lt;/p&gt;
&lt;p&gt;– Command Modification – Change or enrich raw SQL before execution. &lt;/p&gt;
&lt;p&gt;– Multi-Tenant Filtering – Inject filters dynamically for multi-tenant scenarios.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Example 1: Logging SQL Queries&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Let’s log every SQL command executed by EF Core.&lt;/p&gt;
&lt;p&gt;Step 1: Create a SQL Logging Interceptor&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;using Microsoft.EntityFrameworkCore.Diagnostics;
using System.Diagnostics;

public class SqlLoggingInterceptor : DbCommandInterceptor
{
    public override InterceptionResult&amp;lt;DbDataReader&amp;gt; ReaderExecuting(
        DbCommand command, 
        CommandEventData eventData, 
        InterceptionResult&amp;lt;DbDataReader&amp;gt; result)
    {
        Debug.WriteLine($&quot;SQL Executed: {command.CommandText}&quot;);
        return base.ReaderExecuting(command, eventData, result);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Output:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;SQL Executed: SELECT * FROM [Users] WHERE [IsActive] = 1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Step 2: Register the Interceptor&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public class AppDbContext : DbContext
{
    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        optionsBuilder
            .UseSqlServer(&quot;YourConnectionString&quot;)
            .AddInterceptors(new SqlLoggingInterceptor());
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Example 2: Auditing SaveChanges&lt;/p&gt;
&lt;p&gt;Let’s implement automatic auditing of entity modifications.&lt;/p&gt;
&lt;p&gt;Step 1: Define an Auditable Entity Base Class&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public abstract class AuditableEntity
{
    public DateTime ModifiedAt { get; set; }
    public string ModifiedBy { get; set; }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Step 2: Implement a SaveChangesInterceptor&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public class AuditInterceptor : SaveChangesInterceptor
{
    private readonly string _currentUser;

    public AuditInterceptor(string currentUser)
    {
        _currentUser = currentUser;
    }

    public override int SavingChanges(DbContextEventData eventData, int result)
    {
        var context = eventData.Context;
        if (context == null) return base.SavingChanges(eventData, result);

        var entries = context.ChangeTracker
            .Entries&amp;lt;AuditableEntity&amp;gt;()
            .Where(e =&amp;gt; e.State == EntityState.Modified);

        foreach (var entry in entries)
        {
            entry.Entity.ModifiedAt = DateTime.UtcNow;
            entry.Entity.ModifiedBy = _currentUser;
        }

        return base.SavingChanges(eventData, result);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Final Thoughts&lt;/p&gt;
&lt;p&gt;EF Core interceptors are an underutilized gem in the .NET ecosystem. They let you cleanly implement cross-cutting concerns without muddying your core application code. And because they follow the Decorator Pattern, you get flexibility, maintainability, and testability.&lt;/p&gt;
&lt;p&gt;Whether you need to log queries, audit changes, enforce soft deletes, or handle custom behavior, interceptors offer a powerful and elegant solution.&lt;/p&gt;
&lt;p&gt;References&lt;br&gt;
EF Core Documentation – Interceptors&lt;br&gt;
&lt;a rel=&quot;nofollow&quot; href=&quot;https://learn.microsoft.com/en-us/ef/core/logging-events-diagnostics/interceptors&quot;&gt;https://learn.microsoft.com/en-us/ef/core/logging-events-diagnostics/interceptors&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;EF Core 3.0 Release Notes (Interceptors introduced)&lt;br&gt;
&lt;a rel=&quot;nofollow&quot; href=&quot;https://learn.microsoft.com/en-us/ef/core/what-is-new/ef-core-3.0/whatsnew&quot;&gt;https://learn.microsoft.com/en-us/ef/core/what-is-new/ef-core-3.0/whatsnew&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;EF Core SaveChanges Interceptor Docs&lt;br&gt;
&lt;a rel=&quot;nofollow&quot; href=&quot;https://learn.microsoft.com/en-us/ef/core/logging-events-diagnostics/interceptors#savechanges-interception&quot;&gt;https://learn.microsoft.com/en-us/ef/core/logging-events-diagnostics/interceptors#savechanges-interception&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Decorator Pattern - Refactoring Guru (for pattern explanation)&lt;br&gt;
&lt;a rel=&quot;nofollow&quot; href=&quot;https://refactoring.guru/design-patterns/decorator&quot;&gt;https://refactoring.guru/design-patterns/decorator&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;EF Core GitHub Repository (for latest updates and issues)&lt;br&gt;
&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/dotnet/efcore&quot;&gt;https://github.com/dotnet/efcore&lt;/a&gt;&lt;/p&gt;
</description>
<category>C#</category>
<guid isPermaLink="true">https://coderlegion.com/1602/ef-core-interceptors-supercharge-your-data-access-with-the-decorator-pattern</guid>
<pubDate>Thu, 20 Mar 2025 08:25:11 +0000</pubDate>
</item>
<item>
<title>EF Core: Lazy Loading, Eager Loading, and Loading Data on Demand</title>
<link>https://coderlegion.com/1401/ef-core-lazy-loading-eager-loading-and-loading-data-on-demand</link>
<description>&lt;p&gt;When working with &lt;strong&gt;Entity Framework Core (EF Core),&lt;/strong&gt; there are different strategies for loading related data from the database.&lt;/p&gt;
&lt;p&gt;So, how do you load a collection or other &lt;strong&gt;related objects?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Here are the main options:&lt;/p&gt;
&lt;p&gt;1️⃣ Eager Loading (Include)&lt;/p&gt;
&lt;p&gt;Loads related data immediately with the main entity using .Include().&lt;/p&gt;
&lt;p&gt;Example: Load an Order with its Customer&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;var order = context.Orders
    .Include(o =&amp;gt; o.Customer) // Load related Customer
    .First();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Example: Load a Customer with Their Orders&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;var customer = context.Customers
    .Include(c =&amp;gt; c.Orders) // Load related Orders
    .First();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;✅Pros:&lt;/p&gt;
&lt;p&gt;✔ Avoids multiple database queries (N+1 problem)&lt;/p&gt;
&lt;p&gt;✔ Simple and easy to use&lt;/p&gt;
&lt;p&gt;❌ Cons:&lt;/p&gt;
&lt;p&gt;❌ Can load unnecessary data, increasing query size&lt;/p&gt;
&lt;p&gt;❌ Slower if you load too many relationships&lt;/p&gt;
&lt;p&gt;2️⃣ Explicit Loading (Entry().Reference() / Entry().Collection())&lt;/p&gt;
&lt;p&gt;Loads related data on demand (manually) using .Entry().&lt;/p&gt;
&lt;p&gt;Example: Load a Single Related Entity&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;var order = context.Orders.First();

// Load related Customer manually
context.Entry(order).Reference(o =&amp;gt; o.Customer).Load();

Console.WriteLine(order.Customer.Name);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Example: Load a Collection of Related Entities&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;var customer = context.Customers.First();

// Load related Orders manually
context.Entry(customer).Collection(c =&amp;gt; c.Orders).Load();

Console.WriteLine($&quot;Orders count: {customer.Orders.Count}&quot;);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;✅ Pros:&lt;/p&gt;
&lt;p&gt;✔ More control over when data is loaded&lt;/p&gt;
&lt;p&gt;✔ Avoids unnecessary queries&lt;/p&gt;
&lt;p&gt;❌ Cons:&lt;/p&gt;
&lt;p&gt;❌ Requires additional queries&lt;/p&gt;
&lt;p&gt;❌ Must remember to load data explicitly&lt;/p&gt;
&lt;p&gt;3️⃣ Lazy Loading (Requires Proxies)&lt;/p&gt;
&lt;p&gt;Loads related data only when accessed.&lt;/p&gt;
&lt;p&gt;Install :&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;dotnet add package Microsoft.EntityFrameworkCore.Proxies
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Steps to Enable Lazy Loading&lt;/p&gt;
&lt;p&gt;Enable in DbContext:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;protected override void OnConfiguring(DbContextOptionsBuilder options)
{
    options.UseLazyLoadingProxies().UseSqlite(&quot;Data Source=mydb.db&quot;);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Make navigation properties virtual&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public virtual Customer Customer { get; set; } = default!;
public virtual List&amp;lt;Order&amp;gt; Orders { get; set; } = new();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Example Usage : &lt;/p&gt;
&lt;p&gt;var order = context.Orders.First();&lt;/p&gt;
&lt;p&gt;// Lazy loaded automatically when accessed&lt;/p&gt;
&lt;p&gt;Console.WriteLine(order.Customer.Name);&lt;/p&gt;
&lt;p&gt;✅ Pros:&lt;/p&gt;
&lt;p&gt;✔ Simplifies code (data is loaded automatically)&lt;/p&gt;
&lt;p&gt;✔ Works without modifying queries&lt;/p&gt;
&lt;p&gt;❌ Cons:&lt;/p&gt;
&lt;p&gt;❌ Requires proxies (extra package)&lt;/p&gt;
&lt;p&gt;❌ Can lead to multiple database hits (N+1 problem)&lt;/p&gt;
&lt;p&gt;4️⃣ Projection (Select with Anonymous Types)&lt;/p&gt;
&lt;p&gt;Loads only specific data instead of full entities.&lt;/p&gt;
&lt;p&gt;Example: Load Only Needed Fields&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;var orders = context.Orders
    .Select(o =&amp;gt; new 
    { 
        o.OrderNumber, 
        CustomerName = o.Customer.Name 
    })
    .ToList();

foreach (var order in orders)
{
    Console.WriteLine($&quot;{order.OrderNumber} - {order.CustomerName}&quot;);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;✅ Pros:&lt;/p&gt;
&lt;p&gt;✔ Best for performance (loads only required fields)&lt;/p&gt;
&lt;p&gt;✔ Reduces unnecessary data transfer&lt;/p&gt;
&lt;p&gt;❌ Cons:&lt;/p&gt;
&lt;p&gt;❌ Cannot track entities (only works for read-only queries)&lt;/p&gt;
&lt;p&gt;Summary Table&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://coderlegion.com/?qa=blob&amp;amp;qa_blobid=18412293215726215640&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt; Best Practices&lt;br&gt;
Use Eager Loading (Include()) when related data is always needed.&lt;/p&gt;
&lt;p&gt;Use Explicit Loading if you want manual control.&lt;/p&gt;
&lt;p&gt;Use Lazy Loading only if necessary (it can cause performance issues).&lt;/p&gt;
&lt;p&gt;Use Projection (Select) when you need only some fields.&lt;/p&gt;
&lt;p&gt;Source Code&lt;/p&gt;
&lt;p&gt;You can find the complete source code for this tutorial at:&lt;br&gt;
&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/stevsharp/EfCoreExamples&quot;&gt;GitHub Repository&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Here are some useful references to learn more about EF Core : &lt;/p&gt;
&lt;p&gt; Official Microsoft Documentation&lt;br&gt;
Entity Framework Core Loading Related Data&lt;br&gt;
 &lt;a rel=&quot;nofollow&quot; href=&quot;https://learn.microsoft.com/en-us/ef/core/querying/related-data&quot;&gt;https://learn.microsoft.com/en-us/ef/core/querying/related-data&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Covers Eager Loading, Explicit Loading, and Lazy Loading with examples.&lt;br&gt;
EF Core: Configuring Lazy Loading&lt;br&gt;
 &lt;a rel=&quot;nofollow&quot; href=&quot;https://learn.microsoft.com/en-us/ef/core/querying/related-data/lazy&quot;&gt;https://learn.microsoft.com/en-us/ef/core/querying/related-data/lazy&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Explains how to enable Lazy Loading with proxies.&lt;br&gt;
EF Core: Using Include() for Eager Loading&lt;br&gt;
 &lt;a rel=&quot;nofollow&quot; href=&quot;https://learn.microsoft.com/en-us/ef/core/querying/related-data/eager&quot;&gt;https://learn.microsoft.com/en-us/ef/core/querying/related-data/eager&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Details how to use .Include() and .ThenInclude().&lt;br&gt;
EF Core Performance Considerations&lt;br&gt;
 &lt;a rel=&quot;nofollow&quot; href=&quot;https://learn.microsoft.com/en-us/ef/core/performance/&quot;&gt;https://learn.microsoft.com/en-us/ef/core/performance/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Best practices for optimizing queries and avoiding performance issues like N+1 queries.&lt;/p&gt;
&lt;p&gt; Community &amp;amp; Blog Articles&lt;br&gt;
EF Core Lazy Loading vs. Eager Loading - Which One Should You Use?&lt;br&gt;
 &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.thinktecture.com/en/entity-framework-core/lazy-loading-vs-eager-loading/&quot;&gt;https://www.thinktecture.com/en/entity-framework-core/lazy-loading-vs-eager-loading/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;A detailed comparison of when to use each strategy.&lt;br&gt;
EF Core Best Practices: Avoiding Performance Pitfalls&lt;br&gt;
 &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.michalbialecki.com/2020/11/29/entity-framework-core-performance-best-practices/&quot;&gt;https://www.michalbialecki.com/2020/11/29/entity-framework-core-performance-best-practices/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Covers when to use .Include(), lazy loading, and projection.&lt;/p&gt;
</description>
<category>C#</category>
<guid isPermaLink="true">https://coderlegion.com/1401/ef-core-lazy-loading-eager-loading-and-loading-data-on-demand</guid>
<pubDate>Thu, 06 Mar 2025 16:31:00 +0000</pubDate>
</item>
<item>
<title>EF Core Global Query Filters: A Complete Guide</title>
<link>https://coderlegion.com/1323/ef-core-global-query-filters-a-complete-guide</link>
<description>&lt;p&gt;Entity Framework Core (EF Core) Global Query Filters allow you to apply filters to queries at the model level so that they automatically apply to all queries involving a specific entity. &lt;/p&gt;
&lt;p&gt;This is particularly useful for scenarios like soft deletes, multi-tenancy, and user-based data filtering.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What is a Global Query Filter?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;A global filter is a LINQ predicate that applies automatically to all queries on a specific entity type. &lt;/p&gt;
&lt;p&gt;This means that any time you query the database, EF Core applies the filter implicitly, reducing the need for adding Where clauses manually.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Use Cases&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Soft Deletes – Automatically filter out entities where IsDeleted =&lt;br&gt;
true.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Multi-Tenancy – Ensure that a tenant only accesses their own data.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;User-Based Filtering – Restrict users to see only their own records.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;How to Define a Global Query Filter&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Global filters are defined in the OnModelCreating method inside the &lt;strong&gt;DbContext&lt;/strong&gt; class using the HasQueryFilter method.&lt;/p&gt;
&lt;p&gt;Example : Soft Delete Implementation  ..&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public class BaseEntity
{
    public bool IsDeleted { get; set; }
}

public class Product : BaseEntity
{
    public int Id { get; set; }
    public string Name { get; set; }
}

public class AppDbContext : DbContext
{
    public DbSet&amp;lt;Product&amp;gt; Products { get; set; }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity&amp;lt;Product&amp;gt;()
            .HasQueryFilter(p =&amp;gt; !p.IsDeleted);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt; Now, every query on Products automatically filters out records where &lt;strong&gt;IsDeleted&lt;/strong&gt; = true.&lt;/p&gt;
&lt;p&gt;Querying Products&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;var products = context.Products.ToList(); // Implicitly applies &quot;WHERE IsDeleted = 0&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Bypassing the Global Filter&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;You can explicitly disable the filter using .IgnoreQueryFilters().&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;var allProducts = context.Products.IgnoreQueryFilters().ToList();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Applying Filters Dynamically (Multi-Tenancy Example)&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public class AppDbContext : DbContext
{
    private readonly int _tenantId;

    public AppDbContext(DbContextOptions&amp;lt;AppDbContext&amp;gt; options, ITenantProvider tenantProvider)
        : base(options)
    {
        _tenantId = tenantProvider.GetTenantId();
    }

    public DbSet&amp;lt;Order&amp;gt; Orders { get; set; }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity&amp;lt;Order&amp;gt;()
            .HasQueryFilter(o =&amp;gt; o.TenantId == _tenantId);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt; &lt;strong&gt;Now, EF Core automatically filters records based on _tenantId.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Limitations and Considerations&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Filters do not apply to raw SQL queries (FromSqlRaw).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Navigation properties respect global filters (e.g., if Order has OrderDetails, the filter applies to them too).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Ensure filters do not cause unexpected performance issues on large datasets.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;You can define multiple query filters per entity.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Combining Multiple Filters&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;modelBuilder.Entity&amp;lt;Product&amp;gt;()
    .HasQueryFilter(p =&amp;gt; !p.IsDeleted)
    .HasQueryFilter(p =&amp;gt; p.Status == &quot;Active&quot;);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This results in:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;SELECT * FROM Products WHERE IsDeleted = 0 AND Status = 'Active'
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Disabling All Global Filters&lt;/p&gt;
&lt;p&gt;You can disable all filters for a specific context query using:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;context.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;or &lt;/p&gt;
&lt;pre&gt;&lt;code&gt;var allOrders = context.Orders.IgnoreQueryFilters().ToList();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Conclusion&lt;br&gt;
EF Core’s Global Query Filters help enforce consistent rules across queries without adding redundant Where clauses. They are a powerful feature for soft deletes, multi-tenancy, and user-based access control. However, they should be used carefully, considering potential performance impacts.&lt;/p&gt;
&lt;p&gt;Source Code&lt;/p&gt;
&lt;p&gt;You can find the complete source code for this tutorial at:&lt;br&gt;
 &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/stevsharp/EfCoreAndGlobalFilter&quot;&gt;GitHub Repository&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Here are some useful references to learn more about EF Core Global Query Filters and Soft Delete:&lt;/p&gt;
&lt;p&gt; Official EF Core Documentation&lt;br&gt;
Global Query Filters&lt;br&gt;
 &lt;a rel=&quot;nofollow&quot; href=&quot;https://learn.microsoft.com/en-us/ef/core/querying/filters&quot;&gt;https://learn.microsoft.com/en-us/ef/core/querying/filters&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Entity Framework Core SQLite Provider&lt;br&gt;
 &lt;a rel=&quot;nofollow&quot; href=&quot;https://learn.microsoft.com/en-us/ef/core/providers/sqlite/&quot;&gt;https://learn.microsoft.com/en-us/ef/core/providers/sqlite/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Managing Data with EF Core&lt;br&gt;
 &lt;a rel=&quot;nofollow&quot; href=&quot;https://learn.microsoft.com/en-us/ef/core/&quot;&gt;https://learn.microsoft.com/en-us/ef/core/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Community Articles &amp;amp; Tutorials&lt;br&gt;
EF Core: Global Query Filters for Soft Deletes&lt;br&gt;
 &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.thinktecture.com/en/entity-framework-core/global-query-filters/&quot;&gt;https://www.thinktecture.com/en/entity-framework-core/global-query-filters/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Implementing Soft Delete in EF Core&lt;br&gt;
 &lt;a rel=&quot;nofollow&quot; href=&quot;https://medium.com/swlh/soft-delete-in-entity-framework-core-6c6b7a9784c7&quot;&gt;https://medium.com/swlh/soft-delete-in-entity-framework-core-6c6b7a9784c7&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Using SQLite with EF Core in Console Applications&lt;br&gt;
 &lt;a rel=&quot;nofollow&quot; href=&quot;https://code-maze.com/using-sqlite-with-entity-framework-core/&quot;&gt;https://code-maze.com/using-sqlite-with-entity-framework-core/&lt;/a&gt;&lt;/p&gt;
</description>
<category>C#</category>
<guid isPermaLink="true">https://coderlegion.com/1323/ef-core-global-query-filters-a-complete-guide</guid>
<pubDate>Sun, 02 Mar 2025 09:42:51 +0000</pubDate>
</item>
<item>
<title>When to Use AsSplitQuery() in EF.core</title>
<link>https://coderlegion.com/1255/when-to-use-assplitquery-in-ef-core</link>
<description>&lt;p&gt;When working with Entity Framework Core (EF Core), queries that involve multiple Include() statements can result in a single large query with multiple joins. This can cause performance issues due to data duplication and increased memory usage.&lt;/p&gt;
&lt;h2 id=&quot;what-is-assplitquery-&quot;&gt;What is AsSplitQuery()?&lt;/h2&gt;
&lt;p&gt;AsSplitQuery() tells EF Core to fetch related data using separate SQL queries instead of one large join query. This helps optimize performance for complex queries.&lt;/p&gt;
&lt;h3 id=&quot;step-1-setup-your-model&quot;&gt;&lt;strong&gt;Step 1: Setup Your Model&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;Let’s create a simple e-commerce scenario with Order, Customer, and OrderItem entities.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public class Order
{
    public int Id { get; set; }
    public string OrderNumber { get; set; }
    public Customer Customer { get; set; }
    public List&amp;lt;OrderItem&amp;gt; OrderItems { get; set; } = new();
}

public class Customer
{
    public int Id { get; set; }
    public string Name { get; set; }
}

public class OrderItem
{
    public int Id { get; set; }
    public string ProductName { get; set; }
    public int Quantity { get; set; }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;step-2-configure-the-database-context&quot;&gt;&lt;strong&gt;Step 2: Configure the Database Context&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;We define our DbContext to include the necessary DbSet properties.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public class AppDbContext : DbContext
{
    public DbSet&amp;lt;Order&amp;gt; Orders { get; set; }
    public DbSet&amp;lt;Customer&amp;gt; Customers { get; set; }
    public DbSet&amp;lt;OrderItem&amp;gt; OrderItems { get; set; }

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        optionsBuilder.UseSqlite(&quot;Data Source=app.db&quot;);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;step-3-query-without-assplitquery-&quot;&gt;&lt;strong&gt;Step 3: Query Without AsSplitQuery()&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;Let's say we want to fetch orders with their customers and order items.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;**var orders = await _context.Orders
    .Include(o =&amp;gt; o.Customer)
    .Include(o =&amp;gt; o.OrderItems)
    .ToListAsync();**
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;What Happens Here?&lt;/strong&gt;&lt;br&gt;
EF Core generates a single SQL query with multiple JOIN operations.&lt;br&gt;
If there are many orders and items, this can cause performance issues due to data duplication.&lt;/p&gt;
&lt;h3 id=&quot;step-4-optimize-with-assplitquery-&quot;&gt;&lt;strong&gt;Step 4: Optimize with AsSplitQuery()&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;Now, let's improve performance by using AsSplitQuery().&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;var orders = await _context.Orders
    .Include(o =&amp;gt; o.Customer)
    .Include(o =&amp;gt; o.OrderItems)
    .AsSplitQuery() // Fetches data using multiple queries instead of joins
    .ToListAsync();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;What Happens Now?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;EF Core executes multiple separate SQL queries instead of a single large join.&lt;br&gt;
Less memory is used since duplicate data is avoided.&lt;br&gt;
Performance is improved when dealing with large datasets.&lt;/p&gt;
&lt;h3 id=&quot;step-5-understanding-the-sql-queries&quot;&gt;&lt;strong&gt;Step 5: Understanding the SQL Queries&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Without AsSplitQuery()&lt;/strong&gt;&lt;br&gt;
EF Core runs one big SQL query:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;SELECT o.*, c.*, oi.*
FROM Orders o
LEFT JOIN Customers c ON o.CustomerId = c.Id
LEFT JOIN OrderItems oi ON o.Id = oi.OrderId;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This may lead to repetitive customer data for each order item.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;With AsSplitQuery()&lt;/strong&gt;&lt;br&gt;
EF Core runs separate queries:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;SELECT * FROM Orders;
SELECT * FROM Customers WHERE Id IN (...);
SELECT * FROM OrderItems WHERE OrderId IN (...);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This reduces duplication and improves performance.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;When to Use AsSplitQuery()&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;✅ Use AsSplitQuery() when:&lt;/p&gt;
&lt;p&gt;You include multiple navigation properties (Include()).&lt;br&gt;
You experience performance issues with large datasets.&lt;br&gt;
You want to avoid data duplication.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;❌ Avoid AsSplitQuery() when:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Your queries are already small and fast.&lt;br&gt;
Your database round trips increase due to multiple queries.&lt;/p&gt;
&lt;h3 id=&quot;conclusion&quot;&gt;&lt;strong&gt;Conclusion&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;Using AsSplitQuery() in EF Core can boost performance and reduce memory usage when working with complex queries. It’s a simple yet powerful optimization that can make your applications more efficient.&lt;/p&gt;
</description>
<category>ASP.NET</category>
<guid isPermaLink="true">https://coderlegion.com/1255/when-to-use-assplitquery-in-ef-core</guid>
<pubDate>Wed, 26 Feb 2025 08:10:09 +0000</pubDate>
</item>
</channel>
</rss>