nguyenvanchiens/my-skills · Archived

optimizing-ef-core-queries

Optimize EF Core query performance: detect và fix N+1, chọn AsNoTracking vs tracking, Include vs AsSplitQuery vs Projection, compiled queries, batch update/delete (EF 7+), AsAsyncEnumerable cho streaming. Use khi EF query chậm, sinh nhiều SQL, DB CPU cao, memory pressure từ tracked entities, hoặc khi review code có pattern `.ToList().Where()`, `.Include()` chained nhiều levels, `foreach (await query)` mà không có streaming. Khác với skill `entity-framework-core` (general modeling/migration) — s…

First seen May 9, 2026

Installation

$ npx skills add nguyenvanchiens/my-skills --skill optimizing-ef-core-queries

Summary

  • Optimize EF Core query performance: detect và fix N+1, chọn AsNoTracking vs tracking, Include vs AsSplitQuery vs Projection, compiled queries, batch update/delete (EF 7+), AsAsyncEnumerable cho streaming.
  • Use khi EF query chậm, sinh nhiều SQL, DB CPU cao, memory pressure từ tracked entities, hoặc khi review code có pattern `.ToList().Where()`, `.Include()` chained nhiều levels, `foreach (await query)` mà không có streaming.
  • Khác với skill `entity-framework-core` (general modeling/migration) — skill này focus PERFORMANCE.

Stronger alternatives

This repository is archived — consider an actively maintained alternative.

Similar popular skills

Related neighbors and high-traction skills in the same topics — useful to compare before installing.

Also in this package

Other skills from nguyenvanchiens/my-skills · top by installs.

npx skills add nguyenvanchiens/my-skills

Browse all from nguyenvanchiens/my-skills

More details

Agent compatibility

Declared targets from SKILL.md / docs. Unmarked agents are not listed — the skill may still install via the CLI.

Claude Code Not declared
Cursor Not declared
Codex Not declared
GitHub Copilot Not declared
Windsurf Not declared
Gemini CLI Not declared
Cline Not declared
OpenCode Not declared

Repository health

License LICENSE
Default branch main
Open issues 0
Status Archived

Skill metadata

Parsed from SKILL.md frontmatter.

LicenseMIT

Package contents

Files included with this skill beyond the listing page.

  • skill md SKILL.md 6,066 B
  • docs SUMMARY.md 583 B

History

  1. First seen on skills.sh
  2. First recorded snapshot · 2 installs

SKILL.md

Optimizing EF Core Queries

When to Use

  • EF Core queries are slow or generating too many SQL statements
  • Database CPU/IO is high due to ORM inefficiency
  • N+1 query patterns are detected in logs
  • Large result sets cause memory pressure

When Not to Use

  • The user is using Dapper or raw ADO.NET (not EF Core)
  • The performance issue is database-side (missing indexes, bad schema)
  • The user is building a new data access layer from scratch

Inputs

Input Required Description
Slow EF Core queries Yes The LINQ queries or DbContext usage to optimize
SQL output or logs No EF Core generated SQL or query execution logs

Workflow

Step 1: Enable query logging to see the actual SQL

// In Program.cs or DbContext configuration:
optionsBuilder
    .UseSqlServer(connectionString)
    .LogTo(Console.WriteLine, LogLevel.Information)
    .EnableSensitiveDataLogging()  // shows parameter values (dev only!)
    .EnableDetailedErrors();

Or use the Microsoft.EntityFrameworkCore log category:

{
  "Logging": {
    "LogLevel": {
      "Microsoft.EntityFrameworkCore.Database.Command": "Information"
    }
  }
}

Step 2: Fix N+1 query patterns

The #1 EF Core performance killer. Happens when loading related entities in a loop.

Before (N+1 — 1 query for orders + N queries for items):

var orders = await db.Orders.ToListAsync();
foreach (var order in orders)
{
    // Each access triggers a lazy-load query!
    var items = order.Items.Count;
}

After (eager loading — 1 or 2 queries total):

// Option 1: Include (JOIN)
var orders = await db.Orders
    .Include(o => o.Items)
    .ToListAsync();

// Option 2: Split query (separate SQL, avoids cartesian explosion)
var orders = await db.Orders
    .Include(o => o.Items)
    .AsSplitQuery()
    .ToListAsync();

// Option 3: Explicit projection (best - only fetches needed columns)
var orderSummaries = await db.Orders
    .Select(o => new OrderSummary
    {
        OrderId = o.Id,
        Total = o.Items.Sum(i => i.Price),
        ItemCount = o.Items.Count
    })
    .ToListAsync();

When to use Split vs Single query:

Scenario Use
1 level of Include Single query (default)
Multiple Includes (Cartesian risk) AsSplitQuery()
Include with large child collections AsSplitQuery()
Need transaction consistency Single query

Step 3: Use NoTracking for read-only queries

Change tracking overhead is significant. Disable it when you don't need to update entities:

// Per-query
var products = await db.Products
    .AsNoTracking()
    .Where(p => p.IsActive)
    .ToListAsync();

// Global default for read-heavy apps
services.AddDbContext<AppDbContext>(options =>
    options.UseSqlServer(connectionString)
           .UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking));

Use AsNoTrackingWithIdentityResolution() when the query returns duplicate entities to avoid duplicated objects in memory.

Step 4: Use compiled queries for hot paths

// Define once as static
private static readonly Func<AppDbContext, int, Task<Order?>> GetOrderById =
    EF.CompileAsyncQuery((AppDbContext db, int id) =>
        db.Orders
            .Include(o => o.Items)
            .FirstOrDefault(o => o.Id == id));

// Use repeatedly — skips query compilation overhead
var order = await GetOrderById(db, orderId);

Step 5: Avoid common query traps

Trap Problem Fix
ToList() before Where() Loads entire table into memory Filter first: .Where().ToList()
Count() to check existence Scans all rows Use .Any() instead
.Select() after .Include() Include is ignored with projection Remove Include, use Select only
string.Contains() in Where May not translate, falls to client eval Use EF.Functions.Like() for SQL LIKE
Calling .ToList() inside Select() Causes nested queries Use projection with Select all the way

Step 6: Use raw SQL or FromSql for complex queries

When LINQ can't express it efficiently:

var results = await db.Orders
    .FromSqlInterpolated($@"
        SELECT o.* FROM Orders o
        INNER JOIN (
            SELECT OrderId, SUM(Price) as Total
            FROM OrderItems
            GROUP BY OrderId
            HAVING SUM(Price) > {minTotal}
        ) t ON o.Id = t.OrderId")
    .AsNoTracking()
    .ToListAsync();

Validation

  • SQL logging shows expected number of queries (no N+1)
  • Read-only queries use AsNoTracking()
  • Hot-path queries use compiled queries
  • No client-side evaluation warnings in logs
  • Include/split strategy matches data shape

Common Pitfalls

Pitfall Solution
Lazy loading silently creating N+1 Remove Microsoft.EntityFrameworkCore.Proxies or disable lazy loading
Global query filters forgotten in perf analysis Check HasQueryFilter in model config; use IgnoreQueryFilters() if needed
DbContext kept alive too long DbContext should be scoped (per-request); don't cache it
Batch updates fetching then saving EF Core 7+: use ExecuteUpdateAsync / ExecuteDeleteAsync for bulk operations
String interpolation in FromSqlRaw SQL injection risk — use FromSqlInterpolated (parameterized)